①SurfaveView概述。一般的View控件的UI是在主线程里进行绘制的,通过刷新来进行重绘,刷新的时间为16ms。所以在刷新的过程中如果需要执行的逻辑处理过多,会容易出现卡顿现象,而且也会造成主线程阻塞,引起ANR问题。
所以Google就推出了SurfaceView去解决这些问题。SurfaceView继承View,所以拥有View的特性。SurfaceView有两个子类,分别是GLSurfaceView和VideoView。它们有自己独立的、区别于普通View的绘图表面Surface,通过在Surface上绘制图像,然后将产生的视图数据交给SurfaceFlinger,SurfaceFlinger就负责将这些视图数据进行合成,最终发送到显示设备上,从而显示到屏幕上。
现在假设Activity窗口中有DecorView,DecorView里有TextView、Button和SurfaceView。DecorView及其TextView、Button的UI都是在SurfaceFlinger中的同一个Layer上绘制的,而SurfaceView的UI则在SurfaceFlinger的另一个Layer或LayerBuffer上绘制的,如下图所示。
SurfaceView原理 ②SurfaceView的特点。
●具有独立的绘图表面,所以它的UI绘制在独立的线程中进行,不会造成主线程阻塞;
●View主要用于主动更新,而SurfaceView用于被动更新;
●当自定义View需要频繁刷新或者刷新时需要处理比较复杂逻辑时,可以用SurfaceView;
●SurfaceView在绘图时使用了双缓冲机制。
③SurfaceView的使用。
首先创建自定义SurfaceView并继承SurfaceView类,然后分别实现SurfaceHolder.Callback和Runnable及其他的方法:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
@Override
public void run() {
}
}
这里的SurfaceHolder的作用是用它来保存Surface对象的引用,这样就可以操作Surface,从而进行绘制工作。接下来就是初始化工作:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {
//绘制线程标志
private boolean isDraw;
private SurfaceHolder holder;
//画布
private Canvas canvas;
public MySurfaceView(Context context, AttributeSet attributes) {
super(context,attributes);
initView();
}
public MySurfaceView(Context context,AttributeSet attributes,int defStyleAttr) {
super(context,attributes,defStyleAttr);
initView();
}
public MySurfaceView(Context context,AttributeSet attributes,int defStyleAttr,int defStyleRes) {
super(context,attributes,defStyleAttr);
initView();
}
/**
* 初始化工作
*/
public void initView() {
//获得SurfaceHolder对象
holder=getHolder();
holder.addCallback(this);
……
}
……
}
接着就是通过SurfaceHolder的lockCanvans()方法获得Canvas对象,从而在子线程中进行绘制工作,所以整个MySurfaceHolder代码如下:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
//绘制线程标志
private boolean isDraw;
private SurfaceHolder holder;
//画布
private Canvas canvas;
public MySurfaceView(Context context, AttributeSet attributes) {
super(context, attributes);
initView();
}
public MySurfaceView(Context context, AttributeSet attributes, int defStyleAttr) {
super(context, attributes, defStyleAttr);
initView();
}
public MySurfaceView(Context context, AttributeSet attributes, int defStyleAttr, int defStyleRes) {
super(context, attributes, defStyleAttr);
initView();
}
/**
*初始化工作
*/
public void initView() {
//获得 SurfaceHolder对象
holder=getHolder();
holder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
isDfaw=true;
//需要循环来进行绘制,所以开启子线程
new Thread(trus).start();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
isDraw=false;
}
@Override
public void run() {
while (isDraw) {
doDrawing();
}
}
/**
*绘制工作
*/
public void doDrawing() {
try {
//获取Canvas对象进行绘制
canvas=holder.lockCanvas();
//绘制逻辑
} catch (Exception e) {
e.printStackTrace();
}finally {
if(canvas!=null) {
//对画布内容进行提交
holder.unlockCanvasAndPost(canvas);
}
}
}
}
最后,就可以使用这个自定义SurfaceView去实现想要的需求效果。例如现在想实现一个随着用户手指滑动的轨迹来进行绘图的功能,就先通过Path对象记录用户手指的滑动路径,所以在onTouchEvent()方法中进行:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
……
private Path path;//用户手指滑动的路径
……
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取触摸点的X、Y坐标
int x=(int) event.getX();
int y=(int) event.getY();
switch (event.getAction()){
//手指触摸时事件
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
break;
//手指滑动时事件
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
break;
//手指离开时事件
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
与此同时,别忘了在doDrawing()方法中进行绘制:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnabie {
……
private Path path;//用户手指滑动的路径
private Paint paint;//画笔
……
/**
*绘制工作
*/
public void doDrawing() {
try {
//获取Canvas对象进行绘制
canvas=holder.lockCanvas();
//绘制逻辑
canvas.drawColor(Color.BLACK);
canvas.drawPath(path, paint);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(canvas!=null) {
//对画布内容进行提交
holder.unlockCanvasAndPost(canvas);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取触摸点的X、Y坐标
int x=(int) event.getX();
int y=(int) event.getY();
switch (event.getAction()) {
//手指触摸时事件
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
break;
//手指滑动时事件
case MotionEvent.ACTION_MOVE:
path,lineTo(x,y);
break;
//手指离开时事件
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
这样就实现了一个简单版的绘图板功能了,其他的功能需求也是按这样的思路去实现。