天天看点

Android SurfaceView绘图机制

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>