SurfaceView 的基本使用
SurfaceView 的基本使用
Android中提供了View进行绘图处理,View可以满足大部分的绘图需求,但是有时候,View却显得力不从心,所以Android提供了SurfaceView
给Android开发者,以满足更多的绘图需求。下面就让我们一起来了解一下SurfaceView。
1. SurfaceView的作用
View是通过刷新来重绘视图,Android 系统通过发出VSYNC
信号来进行屏幕的重绘,刷新的时间间隔是16ms,
如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView
来解决这一问题。
SurfaceView 继承自
View
,但拥有独立的绘制表面,它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView来实现。SurfaceView 有两个子类 GLSurfaceView 和 VideoView。
SurfaceView
和View
的区别:
1)View
主要适用于主动更新的情况下,而SurfaceView
主要适用于被动更新,例如频繁地刷新。
2)View
在主线程中对画面进行刷新,而SurfaceView
通常会通过一个子线程来进行页面的刷新。
3)View在绘图时没有使用双缓冲机制,而SufaceView
在底层实现机制中就已经实现了双缓冲机制。
2. SurfaceView的使用
使用 SurfaceView 基本可以按照三个步骤来实现,分别是:创建、初始化、使用。下面我们分别来介绍着三个步骤。
2.1 创建SurfaceView
我们需要自定义一个类MySurfaceView 继承自SurfaceView,并实现两个接口SurfaceHolder.CallBack
和Runnable,代码如下所示:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable { public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // SurfaceView 创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { } // SurfaceView 改变时调用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } // SurfaceView 销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void run() { }}
SurfaceVHolder.Callback 的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,Runnalbe 的run()
方法中写我们子线程中执行的绘图逻辑即可。
2.2 初始化SurfaceView
在自定义的SurfaceView中,通常需要定义3个成员变量:
1)SurfaceHolder mSurfaceHolder 可以控制SurfaceView
的大小,格式,可以监控或者改变SurfaceView。
2)Canvas mCanvas 画布
3)boolean isDrawing 子线程标志位,用来控制子线程。
初始化SurfaceView主要就是定义上述三个成员变量并初始化SurfaceHolder并注册对应的回到方法,代码如下所示:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private boolean mIsDrawing; ... public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化 */ private void init() { // 获取 SurfaceHolder对象 mSurfaceHolder = getHolder(); //注册 SurfaceHolder mSurfaceHolder.addCallback(this); // 设置可以获取焦点 setFocusable(true); // 设置触摸可以获取焦点 setFocusableInTouchMode(true); // 保持屏幕常亮 this.setKeepScreenOn(true); } ...}
2.3 使用SurfaceView
使用SurfaceView也可以分为三个步骤:
1)通过lockCanvas()
方法获得Canvas对象。
2) 在子线程中使用Canvas对象进行绘制。
3)使用unlockCanvasAndPost()
方法将画布内容进行提交。
注意: lockCanvas()
方法获得的Canvas对象仍然是上次绘制的对象,由于我们是不断进行绘制,但是每次得到的Canvas对象都是第一次创建的Canvas对象。
绘制要充分利用SurfaceView
的三个回调方法,在surfaceCreate()
方法中开启子线程进行绘制。在子线程中,使用一个while(isDrawing)
循环来不停地绘制。具体的绘制过程,由lockCanvas()
方法进行绘制,并通过unlockCanvasAndPost(mCanvas)
进行画布内容的提交。
实现代码如下所示:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private boolean mIsDrawing; public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化 */ private void init() { // 获取 SurfaceHolder对象 mSurfaceHolder = getHolder(); //注册 SurfaceHolder mSurfaceHolder.addCallback(this); // 设置可以获取焦点 setFocusable(true); // 设置触摸可以获取焦点 setFocusableInTouchMode(true); // 保持屏幕常亮 this.setKeepScreenOn(true); } // SurfaceView 创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; // 通过线程池来执行 Executors.newCachedThreadPool().execute(this); } // SurfaceView 改变时调用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } // SurfaceView 销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { drawing(); } } private void drawing() { try { // 获取Canvas 画布 mCanvas = mSurfaceHolder.lockCanvas(); // 下面进行内容的绘制 ... } finally { if (mCanvas != null) { mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } }}
mSurfaceHolder.unlockCanvasAndPost(mCanvas)
将这行代码放入finally
代码块中,目的是为了确保内容都能够被提交。
3. 使用案例
自定义SurfaceView 实现一个简易画板,初始代码如下所示:
package com.lx.surfaceview.widget;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;import java.util.concurrent.Executors;/** * create by lx * date 2020/8/24. * description: */public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private boolean mIsDrawing; // 画笔 private Paint mPaint; // 路径 private Path mPath; // 上次的坐标 private float mLastX, mLastY; public MySurfaceView(Context context) { this(context, null); } public MySurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化 */ private void init() { // 获取 SurfaceHolder对象 mSurfaceHolder = getHolder(); //注册 SurfaceHolder mSurfaceHolder.addCallback(this); // 设置可以获取焦点 setFocusable(true); // 设置触摸可以获取焦点 setFocusableInTouchMode(true); // 保持屏幕常亮 this.setKeepScreenOn(true); // 初始化画笔 mPaint = new Paint(); mPaint.setStrokeWidth(10); mPaint.setColor(Color.BLACK); mPaint.setStyle(Paint.Style.STROKE); mPaint.setAntiAlias(true); // 初始化路径 mPath = new Path(); } // SurfaceView 创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; // 通过线程池来执行 Executors.newCachedThreadPool().execute(this); } // SurfaceView 改变时调用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } // SurfaceView 销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { drawing(); } } private void drawing() { try { // 获取Canvas 画布 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; }}
运行效果如下图:
4. 优化
由于 while (isDrawing)
是个死循环,drawing
方法一直在执行,就导致一直在绘制,这样会一直占用CPU资源。
1. 使用sleep休眠,在run方法中进行休眠,代码如下:
public void run() { while (mIsDrawing) { long start = System.currentTimeMillis(); drawing(); long end = System.currentTimeMillis(); if (end - start < 100) { try { Thread.sleep(100 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
使用sleep休眠的方式虽然可以降低CPU占用率,但是每次绘制时都会休眠100ms,绘制过程中有种不跟手的感觉。
2. 使用wait和notify方法,当没有绘制时让线程等待,当有绘制时唤醒线程,代码如下所示:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { ... private boolean mIsWait = true; @Override public void run() { drawing(); synchronized (MySurfaceView.this) { while (mIsDrawing) { if (mIsWait) { try { MySurfaceView.this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } drawing(); } } } ... @Override public boolean onTouchEvent(MotionEvent event) { // 获取当前触摸事件的坐标 float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: synchronized (MySurfaceView.this) { mIsWait = false; MySurfaceView.this.notify(); } // 按下 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: // 抬起 mIsWait = true; break; } return true; }}
手指落下,将mIsWait设置为false表示不需要等待,并唤醒线程。
手指离开,将mIsWait设置为true表示需要等待。
更多相关文章
- android 线程中的ui问题 Handler的基本使用 关于获取动态时间在u
- android UI线程向子线程发送Message
- Android的子线程能更新UI吗?
- Android初始化语言 (init.*.rc、init.conf文件格式)
- android 线程大集合
- Android开发艺术探索—— 第十一章Android的线程和线程池
- 无废话Android之smartimageview使用、android多线程下载、显式意