View
的绘图机制存在如下缺陷
-
View
缺乏双缓冲机制。 - 当程序需要更新
View
上的图片时,程序必须重绘View
上显示的整张图片。 - 新线程无法直接更新 View 组件。
SurfaceView 的绘图机制
SurfaceView
一般会与 SurfaceHolder
结合使用,SurfaceHolder
用于向与之关联的 SurfaceView
上绘图,调用 SurfaceView
的 getHolder()
方法即可获取 SurfaceView
关联的 SurfaceHolder
。
SurfaceHolder
提供了如下方法来获取 Canvas
对象。
-
Canvas lockCanvasO
锁定整个SurfaceView
对象,获取该SurfaceView
上的Canvas
。 -
Canvas lockCanvas(Rect dirty)
锁定SurfaceView
上Rect
划分的区域,获取该区域上的Canvas
。
当对同一个 SurfaceView
调用上面两个方法时,两个方法所返回的是同一个 Canvas
对象。但当程序调用第二个方法获取指定区域的 Canvas
时,SurfaceView
将只对 Rect
所“圈”出来的区域进行更新,通过这种方式可以提高画面的更新速度。
当通过 lockCanvas()
获取了指定 SurfaceView
上的 Canvas
之后,接下来程序就可以调用 Canvas
进行绘图了,Canvas
绘图完成后通过 unlockCanvasAndPost(canvas)
方法来释放绘图、提交所绘制的图形。
需要指出的是,当调用 SurfaceHolder
的 unlockCanvasAndPost()
方法之后,该方法之前所绘制的图形还处于缓冲区中,下一次 lockCanvas()
方法锁定的区域可能会“遮挡”它。
小鱼游动
public class FishView extends SurfaceView implements SurfaceHolder.Callback {
private UpdateViewThread updateThread;
private boolean hasSurface;
private Bitmap back;
private Bitmap[] fishs = new Bitmap[10];
private int fishIndex; // 定义变量记录绘制第几张鱼的图片
// 下面定义两个变量,记录鱼的初始位置
private float initX;
private float initY = 500f;
// 下面定义两个变量,记录鱼的当前位置
private float fishX;
private float fishY = initY;
private float fishSpeed = 12f; // 鱼的游动速度
// 定义鱼游动的角度
private int fishAngle = new Random().nextInt(60);
Matrix matrix = new Matrix();
public FishView(Context ctx, AttributeSet set) {
super(ctx, set);
// 获取该SurfaceView对应的SurfaceHolder,并将该类的实例作为其Callback
getHolder().addCallback(this);
back = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.fishbg);
// 初始化鱼游动动画的10张图片
for (int i = 0; i <= 9; i++) {
try {
int fishId = (int) R.drawable.class.getField("fish" + i).get(null);
fishs[i] = BitmapFactory.decodeResource(ctx.getResources(), fishId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void resume() {
// 创建和启动图像更新线程
if (updateThread == null) {
updateThread = new UpdateViewThread();
if (hasSurface) updateThread.start();
}
}
private void pause() {
// 停止图像更新线程
if (updateThread != null) {
updateThread.requestExitAndWait();
updateThread = null;
}
}
// 当SurfaceView被创建时回调该方法
public void surfaceCreated(SurfaceHolder holder) {
initX = getWidth() + 50;
fishX = initX;
hasSurface = true;
resume();
}
// 当SurfaceView将要被销毁时回调该方法
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;
pause();
}
// 当SurfaceView发生改变时回调该方法
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (updateThread != null) updateThread.onWindowResize(w, h);
}
class UpdateViewThread extends Thread {
// 定义一个记录图像是否更新完成的旗标
private boolean done;
@Override
public void run() {
SurfaceHolder surfaceHolder = FishView.this.getHolder();
// 重复绘图循环,直到线程停止
while (!done) {
// 锁定SurfaceView,并返回到要绘图的Canvas
Canvas canvas = surfaceHolder.lockCanvas(); // ①
// 绘制背景图片
canvas.drawBitmap(back, 0f, 0f, null);
// 如果鱼“游出”屏幕之外,重新初始化鱼的位置
if (fishX < -100) {
fishX = initX;
fishY = initY;
fishAngle = new Random().nextInt(60);
}
if (fishY < -100) {
fishX = initX;
fishY = initY;
fishAngle = new Random().nextInt(60);
}
// 使用Matrix来控制鱼的旋转角度和位置
matrix.reset();
matrix.setRotate(fishAngle);
fishX -= fishSpeed * Math.cos(Math.toRadians(fishAngle));
fishY -= fishSpeed * Math.sin(Math.toRadians(fishAngle));
matrix.postTranslate(fishX, fishY);
canvas.drawBitmap(fishs[fishIndex++ % fishs.length], matrix, null);
// 解锁Canvas,并渲染当前图像
surfaceHolder.unlockCanvasAndPost(canvas); // ②
try {
Thread.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void requestExitAndWait() {
// 把这个线程标记为完成,并合并到主程序线程中
done = true;
try {
join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
void onWindowResize(int w, int h) {
// 处理SurfaceView的大小改变事件
System.out.println("w:" + w + "===h:" + h);
}
}
}
实例:基于 SurfaceView 开发示波器
对于 View 组件,如果程序需要花较长的时间来更新绘图,那么主 UI 线程将会被阻塞,无法响应用户的任何动作;而 SurfaceHlolder 则会启用新的线程去更新 SurfaceView 的绘制,因此不会阻塞主 UI 线程。
MainActivity
public class MainActivity extends AppCompatActivity {
private SurfaceHolder holder;
private Paint paint = new Paint();
int HEIGHT = 400;
int screenWidth;
int X_OFFSET = 5;
int cx = X_OFFSET;
// 实际的Y轴的位置
int centerY = HEIGHT / 2;
Timer timer = new Timer();
TimerTask task;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
screenWidth = metrics.widthPixels;
SurfaceView surface = findViewById(R.id.show);
// 初始化SurfaceHolder对象
holder = surface.getHolder();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(getResources().getDimension(R.dimen.stroke_width));
Button sin = findViewById(R.id.sin);
Button cos = findViewById(R.id.cos);
View.OnClickListener listener = source -> {
drawBack(holder);
cx = X_OFFSET;
if (task != null) {
task.cancel();
}
task = new TimerTask() {
@Override
public void run() {
paint.setColor(Color.GREEN);
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,
cx + (int) paint.getStrokeWidth(), cy + (int) paint.getStrokeWidth()));
canvas.drawPoint(cx, cy, paint);
cx += 2;
if (cx > screenWidth) {
task.cancel();
task = null;
}
holder.unlockCanvasAndPost(canvas);
}
};
timer.schedule(task, 0, 10);
};
sin.setOnClickListener(listener);
cos.setOnClickListener(listener);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
drawBack(holder);
}
@Override
public void surfaceCreated(SurfaceHolder myHolder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
timer.cancel();
}
});
}
private void drawBack(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();
// 绘制白色背景
canvas.drawColor(Color.WHITE);
paint.setColor(Color.BLACK);
// 绘制坐标轴
canvas.drawLine(X_OFFSET, centerY,
screenWidth, centerY, paint);
canvas.drawLine(X_OFFSET, 40f, X_OFFSET, HEIGHT, paint);
holder.unlockCanvasAndPost(canvas);
holder.lockCanvas(new Rect(0, 0, 0, 0));
holder.unlockCanvasAndPost(canvas);
}
}
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/sin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sin" />
<Button
android:id="@+id/cos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cos" />
</LinearLayout>
<SurfaceView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" />
</LinearLayout>
values/dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="stroke_width">3dp</dimen>
</resources>
image.png
摘抄至《疯狂Android讲义(第4版)》
网友评论