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


    View与SurfaceView_第1张图片

SurfaceView 示例

1.画正弦函数
View与SurfaceView_第2张图片
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_第3张图片
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模拟火花粒子的滑动喷射效果

你可能感兴趣的:(View与SurfaceView)