Android提供了View进行绘图处理,View可以满足大部分绘图需求,但在某些时候,却也有心有余而力不足的地方,View是通过刷新来绘制视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新时间间隔为16ms。但是如果在16ms内完成所需执行的操作用户不会感觉到卡顿,而如果执行的操作逻辑太多,特别是频繁的刷新界面,就会不断的阻塞主线程,导致画面卡顿。
Android提供了SurfaceView组件来解决这一问题,其与View的主要区别是:
View主要适用于主动更新的情况下,SurfaceView主要适用于被动更新,例如频繁的刷新。
View在主线程中进行刷新,SurfaceView通常会通过一个子线程来进行页面的刷新。
View绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经使用了双缓冲机制。
总而言之: 如果自定义的View需要频繁的刷新或刷新数据量较大时,就可以考虑使用SurfaceView。
SurfaceView的使用:
步骤一:
创建自定义的SurfaceView继承自SurfaceView,并实现两个接口SurfaceHolder.CallBack 和Runnable
如下:
public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable
通过实现上两个接口,就需要在自定义的View中实现接口的方法,对于SurfaceHolder.CallBack需要实现如下方法:
@Override
public void surfaceCreated(SurfaceHolder holder) {//开启子线程进行绘制
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {// SurfaceView的改变
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) { // SurfaceView的销毁
}
对于Runnable需要实现run 方法。
二:初始化SurfaceView
在自定义的SurfaceView构造方法中,需要对SurfaceView进行初始化,在自定义的SurfaceView中通常需要定义三个变量。
private SurfaceHolder mholder;
private Canvas mCanvas;
private boolean mIsDrawing;// 子线程标志位
初始化就是对SurfaceHolder进行初始化,并且注册SurfaceHolder的回调方法 如下:
mholder= getHolder();
mholder.addCallback(this);
三: 使用SurfaceView :
通过SurfaceHolder对象的lockCanvas()方法,就可以获得当前Canvas绘图的对象,接下来就可以与在View中绘图一样进行绘制了,需注意的是获取到的Canvas对象,而不是一个新的对象。
绘制的时候,充分利用SurfaceView的三个回调方法,在surfaceCreated()方法中开启子线程中进行绘制,而使用while (mIsDrawing)的循环来不停的绘制,而在绘制的具体逻辑中通过lockCanvans ()方法获得Canvans对象进行绘制,并通过unlockCanvasAndPost (mCanvas)方法对画布内容进行提交。
需要注意的是
mholder.unlockCanvasAndPost(mCanvas); 方法放到finally中,保证每次都将内容提交。
实例:
使用SurfaceView实现画图板:
通过path对象来记录手指滑动的路径来进行绘图,在SurfaceView的onTouchEvent中来记录Path路径。
public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private Paint mpaint;
private Path mpath;
private SurfaceHolder mHolder;
private boolean misDrawing;
private Canvas mcanvas;
public SimpleDraw(Context context) {
super(context);
initView();
}
public SimpleDraw(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public SimpleDraw(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public 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(20);
}
@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() {
long stratTime = System.currentTimeMillis();
while(misDrawing){
draw();
}
long endTime = System.currentTimeMillis();
if (endTime-stratTime<100){
try{
Thread.sleep(100-(endTime-stratTime));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
private void draw() {
try {
mcanvas= mHolder.lockCanvas();
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;
}
}
以上代码需要注意的是,需要在子线程的循环中进行优化,没有必要一直调用draw()方法进行绘制,可以在子线程中进行sleep操作,尽可能的节省系统资源。
运行效果如下: