View与SurfaceView

SurfaceView理解 surface可以这样理解:它是内存中一块区域,它是surfaceview不可见那个部分,绘图操作作用于它,然后它就会被显卡之类的显示控制器绘制到屏幕上。
surface是个啥,大概已经有了些概念了。因为它对应了一个内存区,大家都知道,内存区的对象是有生命周期的,可以动态的申请创建和销毁,当然也可能会更新。于是,就有了作用于这个内存区的操作,这些操作就是surfaceCreated/Changed/Destroyed。三个操作放在一起,就是callback,
所以在很多例子里看到,会有callback。
SurfaceView 与 View的比较

  • 双缓冲
  • View适用主动更新,SurfaceView 适用被动更新,如频繁的刷新
  • View主线程 SurfaceView子线程刷新(需要界面迅速更新、对帧率要求较高的情况)
双缓冲
在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你讲重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
SurfaceView模板
import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {// SurfaceHolder private SurfaceHolder mHolder; // 用于绘图的Canvas private Canvas mCanvas; // 子线程标志位 private boolean mIsDrawing; public SurfaceViewTemplate(Context context) { super(context); initView(); }public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); initView(); }public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); }private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //mHolder.setFormat(PixelFormat.OPAQUE); }@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() { while (mIsDrawing) { draw(); } }private void draw() { try { mCanvas = mHolder.lockCanvas(); // draw sth } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }

原理分析
  1. 【View与SurfaceView】SurfaceView的绘图表面的创建过程
    由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来。尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点的,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行流程中去。

    View与SurfaceView
    文章图片
SurfaceView 示例 1.画正弦函数 View与SurfaceView
文章图片
sin.gif 思路很简单:
初始化一个Path,每次draw函数都会重新计算x,y的值,通过Path的moveTo方法,然后通过Canvas的drawPath得到曲线。
public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private int x = 0; private int y = 0; private Path mPath; private Paint mPaint; public SinView(Context context) { super(context); initView(); }public SinView(Context context, AttributeSet attrs) { super(context, attrs); initView(); }public SinView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); }private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); }@Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; mPath.moveTo(0, 400); 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() { while (mIsDrawing) { draw(); x += 3; y = (int) (100*Math.sin(x * Math.PI / 180) + 400); mPath.lineTo(x, y); } }private void draw() { try { mCanvas = mHolder.lockCanvas(); // SurfaceView背景 mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }//使用 setContentView(new SinView(this));

很简单,在SurfaceView模板基础上增加了Path和x,y的控制!
2.按照Path Segment 加入动画绘制 这个示例的View不是继承SurfaceView

View与SurfaceView
文章图片
segmentPath.gif
我们只看第二行第一个如何绘制的,其他都一样
public class PathView1 extends View { //一个开源类,原理后面讲 PathAnimator mPathAnimator; int w,h; Path mPath; Paint mPaint = new Paint(); public PathView1(Context context) { super(context); }public PathView1(Context context, AttributeSet attrs) { super(context, attrs); }public PathView1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { this.w = w; this.h = h; init(); super.onSizeChanged(w, h, oldw, oldh); }private void init(){ //画笔颜色 mPaint.setStrokeWidth(20); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.GREEN); mPaint.setAntiAlias(true); //要绘制的路径 Path path = new Path(); path.addCircle(w / 2, h / 2, w / 2 - 20, Path.Direction.CCW); path.moveTo(w / 2 - 60, h / 2 + 10); path.lineTo(w / 2 - 30, h / 2 + 50); path.lineTo(w / 2 + 50, h / 2 - 60); //路径动画 mPathAnimator = new PathAnimator(path); mPathAnimator.setDuration(3000); //动画时间 mPathAnimator.startDelay(1000); mPathAnimator.addUpdateListener(new PathAnimator.PathAnimatorUpdateListener() { @Override public void onAnimationUpdate(float pathPrecent, Path path) { mPath = path; //更新当前路径 invalidate(); } }); mPathAnimator.start(); }@Override protected void onDraw(Canvas canvas) { if(mPath!=null){ canvas.drawPath(mPath,mPaint); } } }

PathAnimator 原理
  1. 通过构造方法,传入一个Path进去。
public PathAnimator(Path path){ mPath = path; mPathMeasure = new PathMeasure(mPath,false); /** * 初始化路径 * 将复合路径分割保存到list. * 记录路径长度 */ initPath(); //路径动画,能够根据当前时间,计算出当前运动路径 initAnim(); }

具体源码请看:https://github.com/jacky1234/PathAnimator
参考资料
  • Android视图SurfaceView的实现原理分析
  • ANDROID模拟火花粒子的滑动喷射效果

    推荐阅读