美文网首页
SurfaceView入门

SurfaceView入门

作者: 黄烨1121 | 来源:发表于2017-12-08 17:04 被阅读0次

    本章目录

    • Part One:SurfaceView概述
    • Part Two:SurfaceView代码模板
    • Part Three:SurfaceView案例

    Part One:SurfaceView概述

    通常情况下,与用户交互的View和响应时间都是在主线程,也就是UI线程去处理的。而网络访问之类的耗时操作放在子线程中执行,避免阻塞主线程。而耗时操作获取到的数据想要更新UI,要么使用接口回调,要么用Handler之类的发消息。最终,所有更新UI的操作都要在主线程中执行。
    像我们一直在讲的案例里,我们使用了自定义View去绘制一些动画,都是在主线程频繁的刷新,此时会造成一个问题:
    以下摘自《安卓群英传》

    Android提供了View进行绘图处理,View可以满足大部分的绘图需求,但在某些时候也会心有余而力不足。我们知道,View通过刷新来重绘视图,Android 系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔为16ms。如果在16ms内View完成了你所需要执行的所有操作,那么用户在视觉上就不会产生卡顿的感觉;而如果执行的操作逻辑太多,特别是需要频繁刷新的界面上,例如游戏界面,就会不断阻塞主线程,从而导致画面卡顿。很多情况下,在自定义View的log中会看到如下的警告:
    “Skipped 47 frames! The application may be doing too much work on its main thread.”
    为了避免这一问题的产生,Android系统提供了SurfaceView组件。

    View和SurfaceView的区别:

    • View主要使用于被动更新,比如下棋游戏,间隔时间比较长;而SurfaceView主要适用于主动更新,比如视图本身频繁刷新界面。
    • View必须在UI的主线程中更新画面,而surfaceView是在一个新起的单独线程中可以重新绘制画面。
    • View 在绘图时没有使用双缓冲机制,而 SurfaceView 在底层实现机制中就已经实现了双缓冲机制。(注:双缓冲是2D游戏开发中常用的一种机制)。

    Part Two:SurfaceView代码模板

    1. 创建一个自定义HeartSurfaceView继承自SurfaceView,写好相应的构造方法;并且实现SurfaceHolder.Callback(在底层的Surface状态发生变化的时候通知SurfaceView)和Runnable(子线程中要执行的操作)接口,重写几个方法。
    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable{
        public HeartSurfaceView(Context context) {
            this(context, null);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariable();
        }
    
        /**
         * 初始化操作
         */
        private void initVariable() {
    
        }
    
        /**
         * 当Surface第一次创建后会立即调用该函数。一般情况下都是在另外的线程来绘制界面,
         * 所以不要在这个函数中绘制Surface。
         */
        @Override
        public void surfaceCreated(SurfaceHolder holder) {}
    
        /**
         *当Surface的状态(大小和格式)发生变化的时候会调用该函数,
         * 在surfaceCreated调用后该函数至少会被调用一次。
         * 类似于自定义View的onSizeChanged
         */
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            
        }
    
        /**
         * 当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,
         * 一般在该函数中来清理使用的资源。
         */
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {}
    
        /**
         * 子线程中要执行的业务
         */
        @Override
        public void run() {
    
        }
    }
    
    1. 初始化几个通用属性,为将来绘制作准备。除了下面写的之外,可根据具体业务自行添加属性。
        private SurfaceHolder surfaceHolder;//用于获取Surface
        private Canvas canvas;//绘图所使用的Canvas
        private boolean isDrawing;//用于判断子线程是否正在绘制中,true是正在绘制,false是绘制完成
    
        public HeartSurfaceView(Context context) {
            this(context, null);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariable();
        }
    
        /**
         * 初始化操作
         */
        private void initVariable() {
            surfaceHolder = getHolder();
            surfaceHolder.addCallback(this);//注册回调
            setZOrderOnTop(true);//设置画布  背景透明
            surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        }
    
    1. 开始绘制
      首先,用SurfaceHolder的Canvas lockCanvas()方法或者Canvas lockCanvas(Rect dirty)方法获取Canvas对象。如果不需要缓存,用lockCanvas()或者lockCanvas(null)获取对象即可;如果只需要重绘变化的部分,可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。
      然后,在子线程中使用Canvas对象进行绘制,具体逻辑等同于自定义View的onDraw方法。
      最后,调用unlockCanvasAndPost(Canvas canvas)提交,通知系统Surface已经绘制完成,这样系统会把绘制完的内容显示出来。因为在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这种同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.PixelFormat;
    import android.graphics.PorterDuff;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private SurfaceHolder surfaceHolder;//用于获取Surface
        private Canvas canvas;//绘图所使用的Canvas
        private boolean isDrawing;//用于判断子线程是否正在绘制中,true是正在绘制,false是绘制完成
    
        public HeartSurfaceView(Context context) {
            this(context, null);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariable();
        }
    
        /**
         * 初始化操作
         */
        private void initVariable() {
            surfaceHolder = getHolder();
            surfaceHolder.addCallback(this);//注册回调
            setZOrderOnTop(true);//设置画布  背景透明
            surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        }
    
        /**
         * 当Surface第一次创建后会立即调用该函数。一般情况下都是在另外的线程来绘制界面,
         * 所以不要在这个函数中绘制Surface。
         */
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            //开始绘制
            isDrawing = true;
        }
    
        /**
         * 当Surface的状态(大小和格式)发生变化的时候会调用该函数,
         * 在surfaceCreated调用后该函数至少会被调用一次。
         * 类似于自定义View的onSizeChanged
         */
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            new Thread(this).start();
        }
    
        /**
         * 当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,
         * 一般在该函数中来清理使用的资源。
         */
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //结束绘制
            isDrawing = false;
        }
    
        /**
         * 子线程中要执行的业务
         */
        @Override
        public void run() {
            while (isDrawing) {
                drawOnCanvas();
            }
    
        }
    
        //具体的绘制逻辑
        private void drawOnCanvas() {
            //用try{}catch{}finally{}包裹起来,保证无论发生何种意外,都能讲Canvas提交
            try {
                canvas = surfaceHolder.lockCanvas();//获得Canvas对象
                if (canvas != null) {
                    //绘制背景
                    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                    //TODO 绘图操作
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (canvas != null){
                    surfaceHolder.unlockCanvasAndPost(canvas);            }
            }
        }
    }
    

    之后,在TODO部分进行具体的绘制即可。

    Part Three:SurfaceView案例

    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.PixelFormat;
    import android.graphics.Point;
    import android.graphics.PorterDuff;
    import android.graphics.PorterDuffColorFilter;
    import android.graphics.RectF;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    import com.terana.mycustomview.R;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class HeartSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
        private SurfaceHolder surfaceHolder;//用于获取Surface
        private Canvas canvas;//绘图所使用的Canvas
        private boolean isDrawing;//用于判断子线程是否正在绘制中,true是正在绘制,false是绘制完成
        private int offsetX;//偏移量,为了让桃心处于屏幕正中央
        private int offsetY;
        private Bitmap bitmapFlower;
        private RectF rectF;
    
        public HeartSurfaceView(Context context) {
            this(context, null);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariable();
        }
    
        /**
         * 初始化操作
         */
        private void initVariable() {
            surfaceHolder = getHolder();
            surfaceHolder.addCallback(this);//注册回调
            setZOrderOnTop(true);//设置画布  背景透明
            surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
            bitmapFlower = BitmapFactory.decodeResource(getResources(), R.drawable.flower);
            rectF = new RectF();
        }
    
        /**
         * 当Surface第一次创建后会立即调用该函数。一般情况下都是在另外的线程来绘制界面,
         * 所以不要在这个函数中绘制Surface。
         */
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            //开始绘制
            isDrawing = true;
        }
    
        /**
         * 当Surface的状态(大小和格式)发生变化的时候会调用该函数,
         * 在surfaceCreated调用后该函数至少会被调用一次。
         * 类似于自定义View的onSizeChanged
         */
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            offsetX = width / 2;
            offsetY = height / 2 - 88;
            new Thread(this).start();
        }
    
        /**
         * 当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,
         * 一般在该函数中来清理使用的资源。
         */
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //结束绘制
            isDrawing = false;
        }
    
        /**
         * 子线程中要执行的业务
         */
        @Override
        public void run() {
            float angle = 10;
            while (true) {
                Point p = getHeartPoint(angle);
                //循环比较新的坐标位置是否可以创建花朵,为了防止花朵太密集
                for (int i = 0; i < heartPoints.size(); i++) {
                    Point bp = heartPoints.get(i);
                    float distance = (float) Math.sqrt(Math.pow(p.x - bp.x, 2) + Math.pow(p.y - bp.y, 2));
                    if (distance < 48) {
                        isDrawing = false;
                        break;
                    }
                }
                heartPoints.add(p);
                if (angle >= 30) {
                    break;
                } else {
                    angle = angle + 0.2f;
                }
                drawOnCanvas();
            }
        }
    
        //具体的绘制逻辑
        private void drawOnCanvas() {
            //用try{}catch{}finally{}包裹起来,保证无论发生何种意外,都能讲Canvas提交
            try {
                canvas = surfaceHolder.lockCanvas();//获得Canvas对象
                if (canvas != null) {
                    //绘制背景
                    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                    //TODO 绘图操作
                    for (int i = 0; i < heartPoints.size(); i++) {
                        rectF.left = heartPoints.get(i).x - 24;
                        rectF.top = heartPoints.get(i).y - 24;
                        rectF.bottom = rectF.top + 48;
                        rectF.right = rectF.left + 48;
                        Paint p = new Paint();
                        p.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#389deb"), PorterDuff.Mode.SRC_IN));
                        canvas.drawBitmap(bitmapFlower, null, rectF, p);
                    }
                    Thread.sleep(20);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    
        private List<Point> heartPoints = new ArrayList<>();
    
        /**
         * 桃心线位置
         */
        public Point getHeartPoint(float angle) {
            float t = (float) (angle / Math.PI);
            float x = (float) (32 * (16 * Math.pow(Math.sin(t), 3)));
            float y = (float) (-32 * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)));
            return new Point(offsetX + (int) x, offsetY + (int) y);
        }
    }
    

    相关文章

      网友评论

          本文标题:SurfaceView入门

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