Android中的SurfaceView详解

来源(简书)

From: https://www.jianshu.com/p/b037249e6d31

Android中的SurfaceView详解

96 冰鉴IT

2017.02.11 21:52* 字数 1261 阅读 10814评论 4喜欢 62

Android中提供了View进行绘图处理,View可以满足大部分的绘图需求,但是有时候,View却显得力不从心,所以Android提供了SurfaceView给Android开发者,以满足更多的绘图需求。下面就让我们一起来了解一下SurfaceView。

一、为什么要使用SurfaceView

我们知道View是通过刷新来重绘视图,系统通过发出VSSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

View和SurfaceView的区别:

1 . View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。

2 . View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。

3 . View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

这摘录了一段网上对于双缓冲技术的介绍

双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。

二、如何使用SurfaceView

要想使用SurfaceView需要经过创建、初始化、使用三个步骤,下面我们就一步步来说说这三个步骤。

1 . 创建SurfaceView

我们需要自定义一个类继承自SurfaceView,并且实现两个接口以及接口定义的方法,当然,与自定义View类似,还要重写三个构造函数。下面是代码:

public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
	public SurfaceViewTemplate(Context context) {
		this(context, null);
	}

	public SurfaceViewTemplate(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		//创建
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		//改变
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		//销毁
	}

	@Override
	public void run() {
		//子线程
	}
}


前面三个构造函数的写法和自定义View是相同的,接下来的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,最后的run()方法中写我们子线程中执行的绘图逻辑即可。

2 . 初始化SurfaceView

这一步我们主要是定义三个成员变量以备后面绘图时使用,然后初始化这三个成员变量并且注册对应的回调方法。代码如下:

private SurfaceHolder mSurfaceHolder;
//绘图的Canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;

/**
* 初始化View
*/
private void initView(){
	mSurfaceHolder = getHolder();
	//注册回调方法
	mSurfaceHolder.addCallback(this);
	//设置一些参数方便后面绘图
	setFocusable(true);
	setKeepScreenOn(true);
	setFocusableInTouchMode(true);
}

public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	//在三个参数的构造方法中完成初始化操作
	initView();
}


上面的代码很简单,都有注释,相信很容易看懂,这里不再过多解释。

3 . 使用SurfaceView

经过上面两步的准备工作,下面就可以开始使用SurfaceView了。

这一步又可以分为3步来完成:

(1) 通过lockCanvas()方法获得Canvas对象

(2) 在子线程中使用Canvas对象进行绘制

(3) 使用unlockCanvasAndPost()方法将画布内容进行提交

注意: lockCanvas() 方法获得的Canvas对象仍然是上次绘制的对象,由于我们是不断进行绘制,但是每次得到的Canvas对象都是第一次创建的Canvas对象。

SurfaceView的绘制可以使用下面的模板代码来实现,唯一的不同就是绘制逻辑的不同,代码如下:

public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
	private SurfaceHolder mSurfaceHolder;
	//绘图的Canvas
	private Canvas mCanvas;
	//子线程标志位
	private boolean mIsDrawing;
	public SurfaceViewTemplate(Context context) {
		this(context, null);
	}

	public SurfaceViewTemplate(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		initView();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mIsDrawing = true;
		//开启子线程
		new Thread(this).start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mIsDrawing = false;
	}

	@Override
	public void run() {
		while (mIsDrawing){
			drawSomething();
		}
	}
	//绘图逻辑
	private void drawSomething() {
		try {
			//获得canvas对象
			mCanvas = mSurfaceHolder.lockCanvas();
			//绘制背景
			mCanvas.drawColor(Color.WHITE);
			//绘图
		}catch (Exception e){

		}finally {
			if (mCanvas != null){
				//释放canvas对象并提交画布
				mSurfaceHolder.unlockCanvasAndPost(mCanvas);
			}
		}
	}

	/**
	* 初始化View
	*/
	private void initView(){
		mSurfaceHolder = getHolder();
		mSurfaceHolder.addCallback(this);
		setFocusable(true);
		setKeepScreenOn(true);
		setFocusableInTouchMode(true);
	}
}


我们在xml文件中的使用和自定义View是相同的,使用全路径名称即可:

<com.codekong.drawlearning.view.SurfaceViewTemplate
	android:layout_width="match_parent"
	android:layout_height="match_parent" />


三、SurfaceView小案例

下面我们通过两个小案例来展示SurfaceView的使用。先放上效果图



正弦曲线



手写板

1 . 绘制正弦曲线

大体的框架都是上面给的那个代码模板,区别只在于初始化画笔,和具体的绘图逻辑,所以这里不再赘述,直接上代码:

public class SurfaceViewSinFun extends SurfaceView implements SurfaceHolder.Callback, Runnable {
	private SurfaceHolder mSurfaceHolder;
	//绘图的Canvas
	private Canvas mCanvas;
	//子线程标志位
	private boolean mIsDrawing;
	private int x = 0, y = 0;
	private Paint mPaint;
	private Path mPath;
	public SurfaceViewSinFun(Context context) {
		this(context, null);
	}

	public SurfaceViewSinFun(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		mPaint = new Paint();
		mPaint.setColor(Color.BLACK);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setAntiAlias(true);
		mPaint.setStrokeWidth(5);
		mPath = new Path();
		//路径起始点(0, 100)
		mPath.moveTo(0, 100);
		initView();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mIsDrawing = true;
		new Thread(this).start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mIsDrawing = false;
	}

	@Override
	public void run() {
		while (mIsDrawing){
			drawSomething();
			x += 1;
			y = (int)(100 * Math.sin(2 * x * Math.PI / 180) + 400);
			//加入新的坐标点
			mPath.lineTo(x, y);
		}
	}

	private void drawSomething() {
		try {
			//获得canvas对象
			mCanvas = mSurfaceHolder.lockCanvas();
			//绘制背景
			mCanvas.drawColor(Color.WHITE);
			//绘制路径
			mCanvas.drawPath(mPath, mPaint);
		}catch (Exception e){

		}finally {
			if (mCanvas != null){
				//释放canvas对象并提交画布
				mSurfaceHolder.unlockCanvasAndPost(mCanvas);
			}
		}
	}

	/**
	* 初始化View
	*/
	private void initView(){
		mSurfaceHolder = getHolder();
		mSurfaceHolder.addCallback(this);
		setFocusable(true);
		setKeepScreenOn(true);
		setFocusableInTouchMode(true);
	}
}


2 . 手写板(随手指绘制轨迹)

这个主要是涉及到触摸事件,在手指按下时将Path的起始点移动到按下的坐标点,手指移动时将移动的坐标点加入Path中,其他的代码是相同的。代码如下:

public class SurfaceViewHandWriting extends SurfaceView implements SurfaceHolder.Callback, Runnable {
	private SurfaceHolder mSurfaceHolder;
	//绘图的Canvas
	private Canvas mCanvas;
	//子线程标志位
	private boolean mIsDrawing;
	//画笔
	private Paint mPaint;
	//路径
	private Path mPath;
	private static final String TAG = "pyh";
	public SurfaceViewHandWriting(Context context) {
		this(context, null);
	}

	public SurfaceViewHandWriting(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SurfaceViewHandWriting(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		mPaint = new Paint();
		mPaint.setColor(Color.BLACK);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeWidth(5);
		mPaint.setAntiAlias(true);
		mPath = new Path();
		mPath.moveTo(0, 100);
		initView();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mIsDrawing = true;
		new Thread(this).start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		mIsDrawing = false;
	}

	@Override
	public void run() {
		while (mIsDrawing) {
			long start = System.currentTimeMillis();
			drawSomething();
			long end = System.currentTimeMillis();
			if (end - start < 100) {
				try {
					Thread.sleep(100 - (end - start));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int x = (int) event.getX();
		int y = (int) event.getY();
		switch (event.getAction()){
			case MotionEvent.ACTION_DOWN:
			mPath.moveTo(x, y);
			break;
			case MotionEvent.ACTION_MOVE:
			mPath.lineTo(x, y);
			break;
			case MotionEvent.ACTION_UP:
			break;
		}
		return true;
	}

	/**
	* 初始化View
	*/
	private void initView(){
		mSurfaceHolder = getHolder();
		mSurfaceHolder.addCallback(this);
		setFocusable(true);
		setKeepScreenOn(true);
		setFocusableInTouchMode(true);
	}

	private void drawSomething() {
		try {
			//获得canvas对象
			mCanvas = mSurfaceHolder.lockCanvas();
			//绘制背景
			mCanvas.drawColor(Color.WHITE);
			//绘制路径
			mCanvas.drawPath(mPath, mPaint);
		}catch (Exception e){

		}finally {
			if (mCanvas != null){
				//释放canvas对象并提交画布
				mSurfaceHolder.unlockCanvasAndPost(mCanvas);
			}
		}
	}
}


上面还有一个细节,在绘制的时候,我们并没有让线程一直运行,而是让它休眠一会,从而节约系统资源,一般建议判断的阈值为50-100之间即可保证用户体验同时节约系统资源。

以上就是SurfaceView的使用介绍,希望可以帮助到需要的人。

Link: http://www.asm32.net/article_details.aspx?id=7342


浏览次数 0 发布时间 2019-01-31 00:19:42 从属分类 Android 安卓 【评论】【 】【打印】【关闭
 
| www.asm32.net | 2006版 | 资料中心 | linux | asm/asm32 | C/C++ | VC++ | java | Python | 书签 | ASP.Net书签 | 京ICP备09029108号-1