SurfaceView一般会与SurfaceHolder结合使用,SurfaceHolder用于向与之关联的SurfaceView上绘图,调用SurfaceView的getHolder()方法即可获取SurfaceView关联的SurfaceHolder.
SurfaceHolder提供了如下方法来获取Canvas对象.
> Canvas lockCanvas(): 锁定整个SurfaceView对象,获取该Surface上的Canvas.
> Canvas lockCanvas(Rect dirty): 锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas.
当对同一个SurfaceView调用上面两个方法时,两个方法所返回的是同一个Canvas对象.但当程序调用第二个方法获取指定区域的Canvas是,SurfaceView将只对Rect所"圈"出来的区域进行更新,通过这种方式可以提高画面的更新速度.
当通过lockCanvas()获取指定了SurfaceView上的Canvas之后,接下来程序就可以调用Canvas进行绘图了,Canvas绘图完成后通过如下方法来释放绘图,提交所绘制的图形:
> unlockCanvasAndPost(canvas);
需要指出的是,当调用SurfaceHolder的unlockCanvasAndPost方法之后,该方法之前所绘制的图形还处于缓冲之中,下一次lockCanvas()方法锁定的区域可能会"遮挡"它.
下面的程序示范了SurfaceView的绘图机制.
package com.example.canvastest;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
public class SurfaceViewActivity extends Activity {
/** SurfaceHolder负责维护SurfaceView绘制的内容 */
private SurfaceHolder holder;
private Paint paint;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surfaceview);
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_view);
// 初始化SurfaceHolder对象
holder = surfaceView.getHolder();
paint = new Paint();
holder.addCallback(new Callback() {
/** 当surface将要被销毁时调用该方法 */
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
/** 当surface被创建时回调该方法 */
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 锁定整个SurfaceView
Canvas canvas = holder.lockCanvas();
// 获得Bitmap位图
Bitmap bmp = BitmapFactory.decodeResource(
SurfaceViewActivity.this.getResources(),
R.drawable.bg02);
// 绘制背景
canvas.drawBitmap(bmp, 0, 0, null);
// 绘制完成,释放画布,提交修改
holder.unlockCanvasAndPost(canvas);
// 重新锁定一次,"持久化"上次所绘制的内容
holder.lockCanvas(new Rect(0, 0, 0, 0));
holder.unlockCanvasAndPost(canvas);
}
/** 当一个surface的格式或大小发生改变时回调该方法 */
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
// 为surfaceView的触摸时间绑定监听器
surfaceView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 只监听按下事件
if (event.getAction() == MotionEvent.ACTION_DOWN) {
int cx = (int) event.getX();
int cy = (int) event.getY();
// 锁定SurfaceView的局部区域,只更新局部内容
Canvas canvas = holder.lockCanvas(new Rect(cx - 50,
cy - 50, cx + 50, cy + 50));
// 保持Canvas当前状态
canvas.save();
// 旋转画布
canvas.rotate(30, cx, cy);
// 画笔颜色
paint.setColor(Color.RED);
// 绘制红色方块
canvas.drawRect(new Rect(cx - 40, cy - 40, cx, cy), paint);
// 恢复Canvas之前保存的状态
canvas.restore();
// 画笔颜色
paint.setColor(Color.GREEN);
// 绘制绿色方块
canvas.drawRect(new Rect(cx, cy, cx + 40, cy + 40), paint);
//绘制完成,释放画布,提交修改
holder.unlockCanvasAndPost(canvas);
}
return false;
}
});
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
上面的程序还为SurfaceHolder添加了一个Callback示例,该Callback中定义了如下三个方法:
> void surfaceChanged(SurfaceHolder holder, int format, int width, int height): 当一个surface的格式或大小发生改变时回调该方法.
> void surfaceCreated(SurfaceHolder holder): 当surface被创建时回调该方法.
> void surfaceDestroyed(SurfaceHolder holder): 当surface将要被销毁时回调该方法.
这里有一个需要注意的地方,为了避免背景图片被下一次lockCanvas()遮挡,程序先调用了lockCanvas(new Rect(0, 0, 0, 0));,本次lockCanvas会"遮挡"上次lockCanvas所绘制
的图形,但由于本次lockCanvas的区域为new Rect(0, 0, 0, 0),因此这里绘制的背景以后不会被遮挡了.
SurfaceView上的"遮挡"有点类似于Flash上的"蒙版"的概念,第一次绘制的图形被第二次的lockCanvas"遮挡"了;第三次lockCanvas时又可能"遮挡"第二次lockCanvas的区域,但不可能"遮挡"第一次lockCanvas的区域;如果第二次lockCanvas"遮挡"的区域又被第三次lockCanvas所"遮挡",那么原来第一次drawCanvas所绘制的图形可能"显露"出来.
SurfaceView与普通View还有一个重要的区别: View的绘图必须在当前UI线程中进行;但SurfaceView就不会存在这个问题,因此SurfaceView的绘图是由SurfaceHolder来完成的.
对于View组件,如果程序需要花较长的时间来更新绘制,那么主UI线程将会被阻塞,无法响应用户的任何动作;而SurfaceHolder则会启动新的线程去更新SurfaceView的绘制,因此不会阻塞主UI线程.
一般来说,如果程序或游戏界面的动画元素较多,而且很多都需要通过定时器来控制这些动画元素的移动,就可以考虑使用SurfaceView,而不是View.
因为View的绘制机制存在如下缺陷:
> View缺乏双缓冲机制.
> 当程序需要更新View上的图像时,程序必须重绘View上显示的整张图片.
> 新线程无法直接更新View组件.
由于View存在上述缺陷,所以通过自定义View来实现绘图,尤其是游戏中的绘图时性能并不好.所以Android提供了一个SurfaceView来代替View,在实现游戏绘画方面,SurfaceView比View更加出色,因此一般推荐使用SurfaceView.
下面是一个示波器程序例子:
package com.example.canvastest;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class ShowWaveActivity extends Activity {
/** SurfaceHolder负责维护SurfaceView绘制的内容 */
private SurfaceHolder holder;
private Paint paint;
final int WIDTH = 320;
final int HEIGHT = 320;
final int X_OFFSET = 5;
private int cx = X_OFFSET;
// 实际的Y轴的位置
private int centerY = HEIGHT / 2;
Timer timer = new Timer();
TimerTask task = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_showwave);
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_view);
// 初始化SurfaceHolder对象
holder = surfaceView.getHolder();
// 初始化画笔
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(2);
Button sin = (Button) findViewById(R.id.sin);
Button cos = (Button) findViewById(R.id.cos);
OnClickListener clickListener = new OnClickListener() {
@Override
public void onClick(final View source) {
DrawBack(holder);
cx = X_OFFSET;
if (task != null) {
task.cancel();
}
task = new TimerTask() {
@Override
public void run() {
int cy = source.getId() == R.id.sin ? centerY
- (int) (100 * Math.sin((cx - 5) * 2 * Math.PI
/ 150))
: centerY
- (int) (100 * 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 ++;
if(cx > WIDTH){
task.cancel();
task = null;
}
holder.unlockCanvasAndPost(canvas);
}
};
timer.schedule(task, 0 , 30);
}
};
sin.setOnClickListener(clickListener);
cos.setOnClickListener(clickListener);
holder.addCallback(new Callback() {
/** 当surface将要被销毁时调用该方法 */
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
DrawBack(holder);
}
/** 当surface被创建时回调该方法 */
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
/** 当一个surface的格式或大小发生改变时回调该方法 */
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
}
private void DrawBack(SurfaceHolder holder) {
// 锁定画布
Canvas canvas = holder.lockCanvas();
// 绘制白色背景
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
p.setColor(Color.BLACK);
p.setStrokeWidth(2);
// 绘制坐标轴
canvas.drawLine(X_OFFSET, centerY, WIDTH, centerY, p);
canvas.drawLine(X_OFFSET, 40, X_OFFSET, HEIGHT, p);
// 释放画布,提交修改
holder.unlockCanvasAndPost(canvas);
holder.lockCanvas(new Rect(0, 0, 0, 0));
holder.unlockCanvasAndPost(canvas);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<Button
android:id="@+id/sin"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="sin"
/>
<Button
android:id="@+id/cos"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="cos"
/>
</LinearLayout>
</LinearLayout>