Android SurfaceView绘图机制

来源:互联网 发布:腹黑兔子自古以来知乎 编辑:程序博客网 时间:2024/05/01 23:53

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;@Overrideprotected 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将要被销毁时调用该方法 */@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}/** 当surface被创建时回调该方法 */@Overridepublic void surfaceCreated(SurfaceHolder holder) {// 锁定整个SurfaceViewCanvas 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的格式或大小发生改变时回调该方法 */@Overridepublic void surfaceChanged(SurfaceHolder holder, int format,int width, int height) {}});// 为surfaceView的触摸时间绑定监听器surfaceView.setOnTouchListener(new OnTouchListener() {@Overridepublic 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;@Overrideprotected 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() {@Overridepublic void onClick(final View source) {DrawBack(holder);cx = X_OFFSET;if (task != null) {task.cancel();}task = new TimerTask() {@Overridepublic 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将要被销毁时调用该方法 */@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {DrawBack(holder);}/** 当surface被创建时回调该方法 */@Overridepublic void surfaceCreated(SurfaceHolder holder) {}/** 当一个surface的格式或大小发生改变时回调该方法 */@Overridepublic 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>

0 0