SurfaceView和View的区别 SurfaceView和View很相似,但是也有所不同,它们的区别主要体现在以下几点:
View适用于主动更新的情况,而SurfaceView主要适用于被动更新,例如频繁的刷新。
View是在主线程中对界面进行刷新的,而SurfaceView通过会通过一个子线程来进行页面的刷新。
View在绘图的时候没有采用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制。
网上对于双缓冲技术的介绍:
1 双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
建议阅读:android双缓冲绘图技术分析 &双缓冲技术绘图 &Android 绘图时实现双缓冲
为什么使用SurfaceView Android系统中当帧率不低于60fps,也就是每一帧的绘制时间不超过16ms的时候,用户才不会感觉到卡顿出现,一帧如果在16ms内没有绘制完成,那么剩下的操作就会放到下一帧的时间去执行,导致下一帧没法绘制,这时候就出现了掉帧现象,这时候我们在32ms内看到的会是同一副画面,从而感觉到卡顿。View是通过onDraw()方法更新视图的(如果需要更新视图,必须我们主动的去调用invalidate()或者postInvalidate()方法来走一次onDraw()方法)。如果在View的onDraw()方法中执行的逻辑太多,超过了16ms,用户就会感觉到画面的卡顿。
View中的onDraw()方法是运行在主线程的,执行onDraw方法的时候会轻微阻塞主线程,对于频繁刷新的页面,onDraw方法会频繁的调用,这时候如果View的onDraw()方法比较耗时(方法中执行的逻辑太多),会导致主线程阻塞时间变长,影响程序的响应应速度,对用户事件的响应变得迟钝,降低了用户体验。比如需要频繁的刷新页面的游戏页面,onDraw方法频繁调用,再加之内部执行的操作比较多,对用户的点击事件的处理就会变得迟缓。
为了缓解和解决上述的两个问题,Android提供了SurfaceView。SurfaceView采用了双缓冲机制,提高了绘制的速度。SurfaceView可以在主线程之外的线程中向屏幕上绘图,这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。
SurfaceView的适用场景 根据上面的分析,我们可以知道,如果我们自定义的View需要频繁的刷新,也就是onDraw方法会频繁的调用,或者刷新数据的时候处理的数据量比较大,这个时候就可以考虑使用SurfaceView来代替View了。需要注意的是SurfaceView也是View派生来的。
SurfaceView的使用 SurfaceView的使用存在的着一套模版代码,大部分的SurfaceView的绘图操作都可以使用这套模版代码来进行编写。
多数情况下可以通过一下步骤来创建一个SurfaceView的模版:
创建自定义的SurfaceView继承自SurfaceView,并实现两个接口-SurfaceHolder.Callback和Runnable。代码如下所示:
1 public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable
需要实现SurfaceHolder.Callback中的方法,需要实现的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { }
分别对应着SurfaceView的创建,改变和销毁过程。 另外还需要实现Runnable接口的run方法。
在SurfaceView的构造方法中,对SurfaceView进行初始化操作。通常需要在SurfaceView中定义以下的三个成员变量:
1 2 3 private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing;
初始化方法就是对SurfaceHolder进行初始化,初始化代码如下所示:
1 2 mHolder = getHolder(); mHolder.addCallback(this);
代码中初始化了Holder,并注册了回掉方法。 mCanvas是用来进行绘图操作的,而mIsDrawing是用来控制运行onDraw方法的子线程的。
通过SurfaceHolder的lockCanvas方法获得canvas,然后利用获得的canvas对象进行绘制操作,每次通过lockCanvas方法获得的都是同一个canvas对象,之前所有的绘制操作都会被保留,如果需要清除之前的操作的话,则可以在绘制前,利用drawColor进行清除操作。
整个SurfaceView的模版代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mHolder; // 用于绘图的Canvas private Canvas mCanvas; // 子线程标志位 private boolean mIsDrawing; public SurfaceViewTemplate(Context context) { super(context); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //mHolder.setFormat(PixelFormat.OPAQUE); } @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) { draw(); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); // draw sth } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }
在surfaceCreated中开启一个子线程,在子线程中执行一个while循环,在while循环中,不断的执行draw方法,在draw方法中中先通过lockCanvas()获得canvas方法获得canvas,然后进行绘制操作,最后通过unlockCanvasAndPost()方法,将绘制保存下来。
SurfaceView的例子 利用SurfaceView绘制正弦曲线 **思路:**使横纵坐标的值满足正弦曲线,然后不断改变横纵坐标的值,根据不断变动的坐标绘制曲线即可,在代码中我们可以用Path记录横纵坐标的值,然后利用canvas绘制路径的方法绘制路径。因为绘制正弦曲线的时候,需要不断的调用draw方法来绘制路径,所以我们可以选择使用SurfaceView来代替View来实现正线曲线的绘制。
完整代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private int x = 0; private int y = 0; private Path mPath; private Paint mPaint; public SinView(Context context) { super(context); initView(); } public SinView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SinView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; mPath.moveTo(0, 400); 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) { draw(); x += 1; y = (int) (100*Math.sin(x * Math.PI / 180) + 400); mPath.lineTo(x, y); if (y == 300 || y == 500) { Log.d("zhangyan","x:"+x); } } } private void draw() { try { mCanvas = mHolder.lockCanvas(); // SurfaceView背景 mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }
利用SurfaceView制作简单的绘图板 **思路:**在onTouchEvent中记录手指滑动的路径,在draw方法中绘制路径即可。
完整代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private boolean mIsClearScreen; private Path mPath; private Paint mPaint; public SimpleDraw(Context context) { super(context); initView(); } public SimpleDraw(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SimpleDraw(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(40); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); Log.d("zhangyan","surfaceCreated"); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; Log.d("zhangyan","surfaceDestroy"); } @Override public void run() { while (mIsDrawing) { long start = System.currentTimeMillis(); draw(); long end = System.currentTimeMillis(); // 50 - 100 if (end - start < 100) { try { Thread.sleep(100 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } Log.d("zhangyan", "draw"); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); //清除canvas的操作 while (mIsClearScreen) { mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); mPath.close();//关闭路径,因为路径中还保存着数据,依然可以绘制 mPath = new Path();//重新获得路径 mIsClearScreen = false; } mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } @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; } public void setClearScreen(boolean clearScreen) { mIsClearScreen = clearScreen; } }
在run方法中对绘制进行了优化,为了避免频繁的绘制消耗系统资源,我们可以控制draw方法执行的频率,通过判断draw方法的执行的时间长度来决定sleep的时间,这是一个通用的做法,100ms是一个经验值,通常取50-100ms。在示例代码中进行了进一步的优化,具体实现可以阅读示例代码。此外,设置了一个清除canvas的标志位,通过设置这个标识位,可以清除canvas上之前绘制的内容,清除操作主要是下面这句代码:
1 mCanvas.drawColor(0, PorterDuff.Mode.CLEAR)
示例代码:SurfaceViewPractice
参考:《Android群英传》 Android中的卡顿现象 Android SurfaceView的基本使用
Android SurfaceView的基本介绍(一) SurfaceView学习笔记->什么是SurfaceView Android SurfaceView入门学习
android双缓冲绘图技术分析 双缓冲技术绘图 Android 绘图时实现双缓冲
上一篇:View的基础知识
下一篇:Canvas中的Layer图层