1.聊一聊雙緩存技術
爲什麼叫“雙緩存”,其實就是有兩個繪圖區,一個是Bitmap的Canvas,另一個則是當前View的Canvas。
使用雙緩存的意義:
1)提高繪圖的性能
2)可以在屏幕上展示繪圖的過程
3)保存繪圖歷史
(1)下面是使用雙緩存來繪製圖形的代碼
private Paint paint;
/**
* 上一個點的座標
*/
private int preX, preY;
/**
* 當前點的座標
*/
private int currentX, currentY;
/**
* Bitmap 緩存區
*/
private Bitmap bitmapBuffer;
private Canvas bitmapCanvas;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (bitmapBuffer == null) {
int width = getMeasuredWidth();//獲取 View 的寬度
int height = getMeasuredHeight(); //獲取 View 的高度
//新建 Bitmap 對象
bitmapBuffer = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmapBuffer);
}
}
// public void run() {
// new Timer().schedule(new TimerTask() {
// @Override
// public void run() {
// postInvalidate();
// }
// }, 0, 1000);
// }
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//將 Bitmap 中的內容繪製在 View 上
canvas.drawBitmap(bitmapBuffer, 0, 0, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下,記錄第一個點的座標
preX = x;
preY = y;
break;
case MotionEvent.ACTION_MOVE:
// 手指移動,記錄當前點的座標
currentX = x;
currentY = y;
bitmapCanvas.drawLine(preX, preY, currentX, currentY, paint);
this.invalidate();
// 當前點的座標成爲下一個點的起始座標
preX = currentX;
preY = currentY;
break;
case MotionEvent.ACTION_UP:
invalidate();
break;
default:
break;
}
return true;
}
}
首先我們創建了一個bitmapBuffer的Bitmap對象,爲了在上面繪圖,我們又創建了一個與之關聯的Canvas對象bitmapCanvas。
在創建Bitmap時需要考慮他的大小,但是此時myView尚未創建,所以重寫了onSizeChanged(),該方法在組件創建後且大小發生改變時回調(在第一次顯示時肯定會調用)。
在上面的代碼中我們使用的是直接繪製直線的方法,但是更好的做法是通過Path來繪圖。
1)可以保存實時的繪圖座標。
2)可以用來繪製更加複雜的圖形。
3)繪圖效率更高。
public class MyView extends View {
private Path path;
private int preX, preY;
private Paint paint;
private Bitmap bitmapBuffer;
private Canvas bitmapCanvas;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmapBuffer, 0, 0, null);
canvas.drawPath(path, paint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (bitmapBuffer == null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
bitmapBuffer = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmapBuffer);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.reset();
preX = x;
preY = y;
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
path.quadTo(preX, preY, x, y);
invalidate();
preX = x;
preY = y;
break;
case MotionEvent.ACTION_UP:
bitmapCanvas.drawPath(path, paint);
invalidate();
break;
default:
break;
}
return true;
}
}
在這裏我們繪製曲線的時候我們採用的是二階貝塞爾曲線,其中貝塞爾曲線的控制點和起點爲同一個點。
(2)在屏幕上繪製矩形
public class MyView extends View {
private int firstX, firstY;//暫時存儲手指落下的位置
private Path path;
private Paint paint;
private Bitmap bitmapBuffer;
private Canvas bitmapCanvas;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmapBuffer, 0, 0, null);
canvas.drawPath(path, paint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (bitmapBuffer == null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
bitmapBuffer = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmapBuffer);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = x;
firstY = y;
break;
case MotionEvent.ACTION_MOVE:
//繪製矩形時,要先清除前一次的結果
path.reset();
if (firstX < x && firstY < y) {
path.addRect(firstX, firstY, x, y, Path.Direction.CCW);
} else if (firstX > x && firstY > y) {
path.addRect(x, y, firstX, firstY, Path.Direction.CCW);
} else if (firstX > x && firstY < y) {
path.addRect(x, firstY, firstX, y, Path.Direction.CCW);
} else if (firstX < x && firstY > y) {
path.addRect(firstX, y, x, firstY, Path.Direction.CCW);
}
invalidate();
break;
case MotionEvent.ACTION_UP:
bitmapCanvas.drawPath(path, paint);
invalidate();
break;
default:
break;
}
return true;
}
}
如果要實現撤銷刪除的功能,可以將每次繪製後的圖形暫存到List中,這樣每次繪製都需要遍歷整個List從而繪製出全部的圖形。
由於Bitmap十分佔用系統資源,撤銷後需要及時調用Bitmap.recycle()方法進行回收。