1. SurfaceView
View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔为16ms
在一些需要频繁刷新,执行很多逻辑操作的时候,超过了16ms
,就会导致卡顿
SurfaceView
继承之View
,但拥有独立的绘制表面,即它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView
SurfaceView
有两个子类GLSurfaceView
和VideoView
SurfaceView
和View
的区别:
View
主要适用于主动更新的情况下,而SurfaceView
主要适用于被动更新,例如频繁地刷新View
在主线程中对画面进行刷新,而SurfaceView
通常会通过一个子线程来进行页面的刷新View在绘图时没有使用双缓冲机制,而
SufaceView
在底层实现机制中就已经实现了双缓冲机制
如果自定义View需要频繁刷新,或者刷新时数据处理量比较大,就 可以考虑使用SurfaceView来取代View了
2. SurfaceView的使用模板
SurfaceView
使用过程有一套模板代码,大部分的SurfaceView
都可以套用
3步走套路:
创建SurfaceView
初始化SurfaceView
使用SurfaceView
2.1 创建SurfaceView
创建一个自定义的SurfaceViewL
,继承之SurfaceView
,并实现两个接口SurfaceHolder.CallBack
和Runnable
代码:
public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback,Runnable{ public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); } @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() { } }
SurfaceHolder.CallBack
有3个方法,分别在SurfaceView
创建,改变,销毁时进行回调
SurfaceHolder.CallBack
还有一个子Callback2
接口,里面添加了一个surfaceRedrawNeeded (SurfaceHolder holder)
方法
当需要重绘SurfaceView
中的内容时,可以使用这个接口。目前还不了解具体的使用场景
2.2 初始化SurfaceView
在自定义的SurfaceView
中,通常需要3个成员变量
SurfaceHolder mSurfaceHolder 可以控制
SurfaceView
的大小,格式,可以监控或者改变SurfaceView
Canvas mCanvas 画布
boolean isDrawing 子线程标志位,用来控制子线程
在构造方法中,对SurfaceHolder mSurfaceHolder
进行初始化
public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); }private void init() { mSurfaceHolder = getHolder();//得到SurfaceHolder对象 mSurfaceHolder.addCallback(this);//注册SurfaceHolder setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true);//保持屏幕长亮}
setFocusable(true)
能否获得焦点setFocusableInTouchMode(true)
能否通过触摸获得焦点
这两个方法都是View
类的方法,可以看看setFocusable与setFocusableInTouchMode差异以及clickable
2.3 使用SurfaceView
利用在2.2拿到的mSurfaceHolder
对象,通过lockCanvas()
方法获得当前的Canvas
注意:lockCanvas()
获取到的Canvas
对象还是上次的Canvas
对象,并不是一个新的对象。之前的绘图都将被保留,如果需要擦除,可以在绘制之前通过drawColor()
方法来进行清屏
绘制要充分利用SurfaceView
的三个回调方法,在surfaceCreate()
方法中开启子线程进行绘制。在子线程中,使用一个while(isDrawing)
循环来不停地绘制。具体的绘制过程,由lockCanvas()
方法进行绘制,并通过unlockCanvasAndPost(mCanvas)
进行画布内容的提交
2.4 完整的模板代码
public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 画布 private Canvas mCanvas; // 子线程标志位 private boolean isDrawing; public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) {//创建 isDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改变 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//销毁 isDrawing = false; } @Override public void run() { while (isDrawing){ drawing(); } } private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); //这里进行内容的绘制 ... }finally { if (mCanvas != null){ mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } }
mSurfaceHolder.unlockCanvasAndPost(mCanvas)
将这行代码放入finally
代码块中,目的是为了确保内容都能够被提交
3. 简单使用
效果还是一个简易的画图板
哈哈,恶搞一下
public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 画布 private Canvas mCanvas; // 子线程标志位 private boolean isDrawing; // 画笔 Paint mPaint; // 路径 Path mPath; private float mLastX, mLastY;//上次的坐标 public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { //初始化 SurfaceHolder mSurfaceHolder mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setStrokeWidth(10f); mPaint.setColor(Color.parseColor("#FF4081")); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); //路径 mPath = new Path(); } @Override public void surfaceCreated(SurfaceHolder holder) {//创建 isDrawing = true; Log.e("surfaceCreated","--"+isDrawing); //绘制线程 new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改变 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//销毁 isDrawing = false; Log.e("surfaceDestroyed","--"+isDrawing); } @Override public void run() { while (isDrawing){ drawing(); } } /** * 绘制 */ private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath,mPaint); } finally { if (mCanvas != null) { mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(x - mLastX); float dy = Math.abs(y - mLastY); if (dx >= 3 || dy >= 3) { mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: break; } return true; } /** * 测量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, 300); } else if (wSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(wSpecSize, 300); } } }
代码主要是从徐医生的Android群英传
中学到的。
由于while (isDrawing)
是个死循环,drawing
方法一直在执行,就导致一直在绘制,徐医生给了一个优化的方案,对run()
方法进行了修改
@Overridepublic void run() { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); while (isDrawing) { drawing(); } long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } }
但对于优化后run()
方法有些疑问,Thread.sleep(100-(end-start))
感觉这行代码并不执行到,因为isDrawing = true
,是个死循环,只有surfaceDestroyed(SurfaceHolder holder)
方法执行时,isDrawing = false
,之后才可以执行到sleep()
方法
根据个人的理解,修改了代码:
public void run() { while (isDrawing) { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); drawing(); long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
这样修改后,虽然sleep()
方法能执行到,但绘制过程有种不跟手的感觉,个人感觉并不用优化,在run()
方法中
4. 自己想的优化
既然不想时时刻在绘制,那就只有在手指在屏幕滑动时,进行绘制
修改代码:
// 修改 1@Overridepublic void surfaceCreated(SurfaceHolder holder) {//创建 drawing(); }//修改onTouchEvent(MotionEvent event)方法//修改 2 case MotionEvent.ACTION_DOWN: isDrawing = true ;//每次开始将标记设置为ture new Thread(this).start();//开启线程 mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break;//修改3 case MotionEvent.ACTION_UP: isDrawing = false;//每次结束将标记设置为false break;//修改4 run()方法@Override public void run() { while (isDrawing){ drawing(); } }
手指落下,将标记设置为true
,并开启线程
手指离开,将标记设置为false
,循环结束后,线程也就停止
热门评论
写得挺好的