View与SurfaceView

作者: jacky123 | 来源:发表于2016-07-06 12:28 被阅读5397次

    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仍然是会参与到宿主窗口的某些执行流程中去。



    SurfaceView 示例

    1.画正弦函数
    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

    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

    参考资料

    相关文章

      网友评论

        本文标题:View与SurfaceView

        本文链接:https://www.haomeiwen.com/subject/gddkjttx.html