Android系统中提供了View可以满足大部分绘图需求,但View主要用于主动更新的情况,用户无法控制其绘制的速度,由于View是通过invalidate方法通知系统去调用view.onDraw方法进行重绘,而Android系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16ms,如果在16ms内View完成不了执行的操作,用户就会看着卡顿,比如当draw方法里执行的逻辑过多,需要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。在自定义View的Logcat中,经常看到如下警告:
Skipped 60 frames! The application may be doing too much work onits main thread
Android提供了SurfaceView用于解决这个问题。SurfaceView另开一个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。
先看下SDK中对其解释:
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。
surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域,只有在这个可见区域内 surface 部分内容才可见,可见区域外的部分不可见。
surface 的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface 的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果 surface 上面有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件之间的透明效果,这会影响性能。
你可以通过 surfaceHolder 接口访问这个surface,getHolder() 方法可以得到这个接口。
surfaceview 变得可见时,surface被创建;surfaceview隐藏前,surface被销毁。这样能节省资源。如果你要查看 surface 被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder) surfaceView 的核心在于提供了两个线程:UI线程和渲染线程。
这里应注意:
1. 所有 SurfaceView 和 SurfaceHolder.Callback 的方法都会在UI线程里调用,一般来说就是应用程序主线程。所以渲染线程所要访问的各种变量应该作同步处理。
2. 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的 surface
SurfaceView继承之View,但拥有独立的绘制表面,即它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播和人脸识别,都可以用SurfaceView。
另外说明一下,SurfaceView有两个子类GLSurfaceView和VideoView
通常情况下,使用以下步骤来创建一个SurfaceView的模板:
创建SurfaceView
创建自定义的SurfaceView继承自SurfaceView,并实现两个接口:SurfaceHolder.Callback和Runnable,通过实现这两个接口,就需要在自定义的SurfaceView中实现接口的方法,对于SurfaceHolder.Callback方法,需要实现如下方法,其实就是SurfaceView的生命周期:
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
@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() {
}
初始化SurfaceView:
private SurfaceHolder mHolder;
private Canvas mCanvas;//绘图的画布
private volatile boolean mIsDrawing;//控制绘画线程的标志位
SurfaceHolder,顾名思义,它里面保存了一个对Surface对象的引用,而我们执行绘制方法本质上就是操控Surface。SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期。(说到底 SurfaceView的生命周期其实就是Surface的生命周期)例如使用 SurfaceHolder来处理生命周期的初始化。
public CustomSurfaceView(Context context) {
super(context);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mHolder = getHolder();//获取SurfaceHolder对象
mHolder.addCallback(this);//注册SurfaceHolder的回调方法
setFocusable(true);// 能否用键盘获得焦点
setFocusableInTouchMode(true);//能否通过触摸获得焦点
this.setKeepScreenOn(true);//屏幕常亮
}
使用SurfaceView:
/**
* Title:
* Description:
* Company: 北京****科技有限公司,010-62538800,[email protected]
*
* @author Created by ylwang on 2018/2/2
*/
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;//绘图的画布
private volatile boolean mIsDrawing;//控制绘画线程的标志位
private Paint mPaint;
private Path mPath;
public CustomSurfaceView(Context context) {
super(context);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
//setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
mPaint = new Paint();
mPaint.setColor(Color.GREEN);//画笔颜色
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setStrokeWidth(6);//画笔宽度
mPaint.setStyle(Paint.Style.STROKE);//空心
mPath = new Path();
mHolder = getHolder();//获取SurfaceHolder对象
mHolder.addCallback(this);//注册SurfaceHolder的回调方法
setFocusable(true);// 能否用键盘获得焦点
setFocusableInTouchMode(true);//能否通过触摸获得焦点
this.setKeepScreenOn(true);//屏幕常亮
}
@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;
}
public static final int TIME_IN_FRAME = 30;
@Override
public void run() {
long start;
int x = 0, y = 0;
drawSth();
while (mIsDrawing) {
start = SystemClock.uptimeMillis();//开始绘制
//绘制sin(x)函数
x += 1;
y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400);
mPath.lineTo(x, y);
drawSth();
while (SystemClock.uptimeMillis() - start <= TIME_IN_FRAME) {
Thread.yield();//线程让出
}
}
}
private void drawSth() {
try {
mCanvas = mHolder.lockCanvas(); //拿到当前画布 然后锁定
mCanvas.drawColor(Color.GRAY);//画布颜色
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);//保证每次都将绘图的内容提交
}
}
}
}
运行结果如下:
private int lastX, lastY, dx, dy;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
mPath.moveTo(lastX, lastY);
break;
case MotionEvent.ACTION_MOVE:
dx = x - lastX;
dy = y - lastY;
if (dx > 3 || dy > 3) {
mPath.quadTo(lastX, lastY, (lastX + x) >> 1, (lastY + y) >> 1);
}
lastX = x;
lastY = y;
break;
default:
break;
}
return true;
}
@Override
public void run() {
long start = 0;
while (mIsDrawing) {
start = SystemClock.uptimeMillis();//开始绘制
drawSth();
while (SystemClock.uptimeMillis() - start <= TIME_IN_FRAME) {
Thread.yield();//线程让出
}
}
}
解释一下:
Path.quadTo(float x1, float y1, float x2, float y2)用于画平滑曲线,该曲线又称为"贝塞尔曲线"(Bezier curve),其中,x1,y1为控制点的坐标值,x2,y2为终点的坐标值;具体参考: Path详细用法
, Path类的lineTo和quadTo的区别
;