雖然前面介紹了使用自定義View來進(jìn)行繪圖,但View的繪圖機(jī)制存在如下兩個缺陷:
View缺乏雙緩存機(jī)制
當(dāng)程序需要更新View上的圖像時,程序必須重繪View上顯示的整張圖片。
由于View存在上面兩個缺陷,所以通過自定義View來實現(xiàn)繪圖、尤其是游戲中的繪圖時性能并不好。Android提供一個SurfaceView來代替View,在游戲繪圖方面,SurfaceView比View更出色,因此一般推薦SurfaceView。
SurfaceView的繪圖機(jī)制
SurfaceView一般會與SurfaceHolder結(jié)合使用,SurfaceHolder用于向與之關(guān)聯(lián)的 SurfaceVew上繪圖,調(diào)用 SurfaceView的 getholder()方法即可獲取SurfaceView關(guān)聯(lián)的SurfaceHolder。
Surface Holder提供了如下方法來獲取 Canvas對象。
Canvas lockCanvas():鎖定整個SurfaceView對象,獲取該 Surface上的 Cvanvas。
Canvas lockcanvas(Rect dirty):鎖定 Surfaceview上Rect劃分的區(qū)域,獲取該Surface上的 Canvas。
當(dāng)對同一個SurfaceView調(diào)用上面兩個方法時,兩個方法所返回的是同一個Canvas對象。但當(dāng)程序調(diào)用第二個方法獲取指定區(qū)域的Canvas時, SurfaceView將只對Rect所“圈”出來的區(qū)域進(jìn)行更新,通過這種方式可以提高畫面的更新速度。
當(dāng)通過lockcanvas()獲取指定了 SurfaceView上的Canvas之后,接下來程序就可以調(diào)用Canvas進(jìn)行繪圖了,Canvas繪圖完成后通過如下方法來釋放繪圖、提交所繪制的圖形:
unlockCanvasAndPost(canvas);
需要指出的是,當(dāng)調(diào)用 SurfaceHolder的unlockCanvasAndPost方法之后,該方法之前所繪制的圖形還處于緩沖之中,下一次 lockCanvas方法鎖定的區(qū)域可能會“遮擋”它。
下面的程序示范了SurfaceView的繪圖機(jī)制。
//SurfaceHolder負(fù)責(zé)維護(hù)SurfaceView上繪制的內(nèi)容
private SurfaceHolder holder;
private Paint paint;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity18);
paint = new Paint();
SurfaceView surfaceView = findViewById(R.id.show);
//初始化SurfaceHolder對象
holder = surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
//鎖定整個SurfaceView
Canvas canvas = holder.lockCanvas();
//繪制背景
Bitmap back = BitmapFactory.decodeResource(Activity18.this.getResources(),
R.drawable.switcher5);
//繪制背景
canvas.drawBitmap(back, 0, 0, null);
//繪制完成,釋放畫布,提交修改
holder.unlockCanvasAndPost(canvas);
//重新鎖一次,避免被下次lockCanvas遮擋
holder.lockCanvas(new Rect(0,0,0,0));
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
//為Surface的觸摸事件綁定監(jiān)聽器
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
//只處理按下事件
if(event.getAction() == MotionEvent.ACTION_DOWN){
int cx = (int) event.getX();
int cy = (int) event.getY();
//鎖定SurfaceView的局部區(qū)域,只更新局部內(nèi)容
Canvas canvas = holder.lockCanvas(new Rect(cx - 50, cy - 50,
cx + 50, cy + 50));
//保存canvas的當(dāng)前狀態(tài)
canvas.save();
//旋轉(zhuǎn)畫布
canvas.rotate(30, cx, cy);
paint.setColor(Color.RED);
//繪制紅色方塊
canvas.drawRect(cx - 40, cy - 40, cx, cy, paint);
//恢復(fù)Canvas之前的保存狀態(tài)
canvas.restore();
paint.setColor(Color.GREEN);
//繪制綠色方塊
canvas.drawRect(cx, cy, cx + 40, cy + 40, paint);
//繪制完成,釋放畫布,提交修改
holder.unlockCanvasAndPost(canvas);
}
return false;
}
});
}

如圖所示的第一次繪制的圖形被第二次的canvas“遮擋”了;第三次 lockCanvas時又可能“遮擋”第二次 lockCanvas的區(qū)域,但不可能“遮擋”第一次 lockCanvas的區(qū)域;如果第二次 lockCanvas“遮擋”的區(qū)域又被第三次lockcanvas所“遮擋”,那么原來第一次 drawCanvas所繪制的圖形可能“顯露”出來。
基于SurfaceView示波器
SurfaceView與普通View還有一個重要的區(qū)別:View的繪圖必須在當(dāng)前UI線程中進(jìn)行---這也是前面程序需要更新View組件時總要采用 Handler處理的原因;但SurfaceView就不會存在這個問題,因此 SurfaceView的繪圖是由 SurfaceHolder來完成的。
對于View組件,如果程序需要花較長的時間來更新繪圖,那么主UI線程將會被阻塞,無法響應(yīng)用戶的任何動作;而SurfaceViewHolder則會啟用新的線程去更新 Surface View的繪制,因此不會阻塞主UI線程。
一般來說,如果程序或游戲界面的動畫元素較多,而且很多都需要通過定時器來控制這些動畫元素的移動,就可以考慮使用SurfaceView,而不是View。例如下面我們使用Surface View開發(fā)一個示波器程序,該程序?qū)鶕?jù)用戶單擊的按鈕在屏幕上自動繪制正弦波或余弦波。
該程序的界面布局很簡單,界面布局中包含兩個按鈕和一個SurfaceView,程序每次繪制時只需要繪制(更新)當(dāng)前點的波形,前面已經(jīng)繪制的波形無須更新,這就利用了Surfaceholder的lockCanvas(Rect r)方法。
private SurfaceHolder holder;
private Paint paint;
final int HEIGHT = 700;
final int WIDTH = 700;
final int X_OFFSET = 5;
private int cx = X_OFFSET;
//實際的Y軸的位置
int centerY = HEIGHT / 2;
Timer timer = new Timer();
TimerTask task = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity19);
final SurfaceView surface = findViewById(R.id.show);
//初始化SurfaceHolder對象
holder = surface.getHolder();
paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setStrokeWidth(5);
Button sin = findViewById(R.id.sin);
Button cos = findViewById(R.id.cos);
sin.setOnClickListener(this);
cos.setOnClickListener(this);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
drawBack(holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
timer.cancel();
}
});
}
@Override
public void onClick(final View view) {
drawBack(holder);
cx = X_OFFSET;
if(task != null){
task.cancel();
}
task = new TimerTask() {
@Override
public void run() {
int cy = view.getId() == R.id.sin ? centerY -
(int)(200 * Math.sin((cx - 5) * 2 * Math.PI / 150))
: centerY - (int)(200 * Math.cos((cx - 5) * 2 * Math.PI / 150));
Canvas canvas = holder.lockCanvas(new Rect(cx, cy - 2, cx + 2, cy + 2));
canvas.drawPoint(cx, cy, paint);
cx += 2;
if(cx > WIDTH){
task.cancel();
task = null;
}
holder.unlockCanvasAndPost(canvas);
}
};
timer.schedule(task, 0, 30);
}
private void drawBack(SurfaceHolder holder){
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
paint.setAntiAlias(true);
//繪制坐標(biāo)
canvas.drawLine(X_OFFSET, centerY, WIDTH, centerY, paint);
canvas.drawLine(X_OFFSET, 40, X_OFFSET, HEIGHT, paint);
holder.unlockCanvasAndPost(canvas);
holder.lockCanvas(new Rect(0,0,0,0));
holder.unlockCanvasAndPost(canvas);
}
