• 24、Android--SurfaceView


    SurfaceView

    Android系统提供View进行绘图处理,View可以满足大部分的绘图需求,但是View有一个弊端,Android通过发出VSYNC信号进行屏幕的重绘,刷新的间隔时间为16ms,如果在16ms内View完成所需的操作,用户视觉上则不会产生卡顿的感觉。而如果执行的逻辑操作太多,特别是频繁刷新界面,那么就会不断阻塞主线程,从而导致画面卡顿。

    Skipped 1188 frames! The application may be doing too much work on its main thread.

    为避免该情况的发生,Android系统提供SurfaceView组件来解决该问题。它和View的区别如下:

    View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁刷新。
    View在主线程中对画面进行刷新,而SurfaceView通常在子线程进行页面刷新。
    View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中已经实现双缓冲机制。

    总结:如果自定义View需要频繁刷新,或者刷新数据量比较大则使用SurfaceView。

    SurfaceView基础

    要想使用SurfaceView需要经过创建、初始化、使用三个步骤

    创建SurfaceView

    我们需要自定义一个类继承自SurfaceView,并且实现两个接口以及接口定义的方法

    public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        public SurfaceViewTemplate(Context context) {
            this(context, null);
        }
    
        public SurfaceViewTemplate(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
           //创建
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            //改变
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //销毁
        }
    
        @Override
        public void run() {
            //子线程
        }
    }
    

    三个构造函数的写法和自定义View是相同的,接下来的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,而run()方法中写我们子线程中执行的绘图逻辑即可。

    初始化SurfaceView

    我们主要是定义三个成员变量以备后面绘图时使用,然后初始化这三个成员变量并且注册对应的回调方法。代码如下:

    private SurfaceHolder mSurfaceHolder;
    //绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;
    public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
    
    private void initView(){
        mSurfaceHolder = getHolder();
        //注册回调方法
        mSurfaceHolder.addCallback(this);
        //设置一些参数方便后面绘图
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
    }
    

    使用SurfaceView

    在使用SurfaceView的时候,可以分为如下三个步骤:

    (1) 通过lockCanvas()方法获得Canvas对象
    (2) 在子线程中使用Canvas对象进行绘制
    (3) 使用unlockCanvasAndPost()方法将画布内容进行提交

    lockCanvas() 方法获得的Canvas对象仍然是上次绘制的对象,由于不断进行绘制,而每次得到的Canvas对象都是第一次创建的Canvas对象。

    public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private SurfaceHolder mSurfaceHolder;
        //绘图的Canvas
        private Canvas mCanvas;
        //子线程标志位
        private boolean mIsDrawing;
        public SurfaceViewTemplate(Context context) {
            this(context, null);
        }
    
        public SurfaceViewTemplate(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView();
        }
    
        @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){
                drawSomething();
            }
        }
        //绘图逻辑
        private void drawSomething() {
            try {
                //获得canvas对象
                mCanvas = mSurfaceHolder.lockCanvas();
                //绘制背景
                mCanvas.drawColor(Color.WHITE);
                //绘图
            }catch (Exception e){
    
            }finally {
                if (mCanvas != null){
                    //释放canvas对象并提交画布
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }
        }
    
        /**
         * 初始化View
         */
        private void initView(){
            mSurfaceHolder = getHolder();
            mSurfaceHolder.addCallback(this);
            setFocusable(true);
            setKeepScreenOn(true);
            setFocusableInTouchMode(true);
        }
    }
    

    在xml文件中的使用和自定义View是相同的,使用全路径名称即可:

    <com.legend.view.SurfaceViewTemplate                                     
          android:layout_width="match_parent"
          android:layout_height="match_parent" />
    

    SurfaceView扩展

    下面我们编写两个案例:绘制正弦函数和手绘板

    绘制正弦函数

    大体的框架都是上面给的那个代码模板,区别只在于初始化画笔,和具体的绘图逻辑,所以这里不再赘述,直接上代码:

    public class SurfaceViewSinFun extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private SurfaceHolder mSurfaceHolder;
        //绘图的Canvas
        private Canvas mCanvas;
        //子线程标志位
        private boolean mIsDrawing;
        private int x = 0, y = 0;
        private Paint mPaint;
        private Path mPath;
        public SurfaceViewSinFun(Context context) {
            this(context, null);
        }
    
        public SurfaceViewSinFun(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mPaint = new Paint();
            mPaint.setColor(Color.BLACK);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setAntiAlias(true);
            mPaint.setStrokeWidth(5);
            mPath = new Path();
            //路径起始点(0, 100)
            mPath.moveTo(0, 100);
            initView();
        }
    
        @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){
                drawSomething();
                x += 1;
                y = (int)(100 * Math.sin(2 * x * Math.PI / 180) + 400);
                //加入新的坐标点
                mPath.lineTo(x, y);
            }
        }
    
        private void drawSomething() {
            try {
                //获得canvas对象
                mCanvas = mSurfaceHolder.lockCanvas();
                //绘制背景
                mCanvas.drawColor(Color.WHITE);
                //绘制路径
                mCanvas.drawPath(mPath, mPaint);
            }catch (Exception e){
    
            }finally {
                if (mCanvas != null){
                    //释放canvas对象并提交画布
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }
        }
    
        /**
         * 初始化View
         */
        private void initView(){
            mSurfaceHolder = getHolder();
            mSurfaceHolder.addCallback(this);
            setFocusable(true);
            setKeepScreenOn(true);
            setFocusableInTouchMode(true);
        }
    }
    

    手写板

    主要是涉及到触摸事件,在手指按下时将Path的起始点移动到按下的坐标点,手指移动时将移动的坐标点加入Path中,其他的代码是相同的。代码如下:

    public class SurfaceViewHandWriting extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private SurfaceHolder mSurfaceHolder;
        //绘图的Canvas
        private Canvas mCanvas;
        //子线程标志位
        private boolean mIsDrawing;
        //画笔
        private Paint mPaint;
        //路径
        private Path mPath;
        private static final String TAG = "pyh";
        public SurfaceViewHandWriting(Context context) {
            this(context, null);
        }
    
        public SurfaceViewHandWriting(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SurfaceViewHandWriting(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mPaint = new Paint();
            mPaint.setColor(Color.BLACK);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(5);
            mPaint.setAntiAlias(true);
            mPath = new Path();
            mPath.moveTo(0, 100);
            initView();
        }
    
        @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) {
                long start = System.currentTimeMillis();
                drawSomething();
                long end = System.currentTimeMillis();
                if (end - start < 100) {
                    try {
                        Thread.sleep(100 - (end - start));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
        @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;
        }
    
        /**
         * 初始化View
         */
        private void initView(){
            mSurfaceHolder = getHolder();
            mSurfaceHolder.addCallback(this);
            setFocusable(true);
            setKeepScreenOn(true);
            setFocusableInTouchMode(true);
        }
    
        private void drawSomething() {
            try {
                //获得canvas对象
                mCanvas = mSurfaceHolder.lockCanvas();
                //绘制背景
                mCanvas.drawColor(Color.WHITE);
                //绘制路径
                mCanvas.drawPath(mPath, mPaint);
            }catch (Exception e){
    
            }finally {
                if (mCanvas != null){
                    //释放canvas对象并提交画布
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }
        }
    }
    
  • 相关阅读:
    Windows Message ID 常量列表大全
    C#中Thread与ThreadPool的比较
    HTML元素隐藏和显示
    Metrics.net + influxdb + grafana 构建WebAPI的自动化监控和预警
    Windbg DUMP分析(原创汇总)
    计算密集型分布式内存存储和运算平台架构
    从.net到java,从基础架构到解决方案。
    C# 泛型集合
    你该怎么选Offer
    C++ 虚拟桌面
  • 原文地址:https://www.cnblogs.com/pengjingya/p/5510110.html
Copyright © 2020-2023  润新知