SurfaceView 的基本使用

       Android中提供了View进行绘图处理,View可以满足大部分的绘图需求,但是有时候,View却显得力不从心,所以Android提供了SurfaceView给Android开发者,以满足更多的绘图需求。下面就让我们一起来了解一下SurfaceView。

  1. SurfaceView的作用

        View是通过刷新来重绘视图,Android 系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

       SurfaceView 继承自 View,但拥有独立的绘制表面,它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView来实现。SurfaceView 有两个子类 GLSurfaceView 和 VideoView。

       SurfaceViewView的区别:

       1)View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新。

       2)View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新。

       3)View在绘图时没有使用双缓冲机制,而SufaceView在底层实现机制中就已经实现了双缓冲机制。

 

     2. SurfaceView的使用

       使用 SurfaceView 基本可以按照三个步骤来实现,分别是:创建、初始化、使用。下面我们分别来介绍着三个步骤。

      2.1 创建SurfaceView

       我们需要自定义一个类MySurfaceView 继承自SurfaceView,并实现两个接口SurfaceHolder.CallBackRunnable,代码如下所示:

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

       SurfaceVHolder.Callback 的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,Runnalbe 的run()方法中写我们子线程中执行的绘图逻辑即可。

     2.2 初始化SurfaceView

       在自定义的SurfaceView中,通常需要定义3个成员变量:

       1)SurfaceHolder mSurfaceHolder 可以控制SurfaceView的大小,格式,可以监控或者改变SurfaceView。

       2)Canvas mCanvas 画布

       3)boolean isDrawing 子线程标志位,用来控制子线程。

       初始化SurfaceView主要就是定义上述三个成员变量并初始化SurfaceHolder并注册对应的回到方法,代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    private SurfaceHolder mSurfaceHolder;    private Canvas mCanvas;    private boolean mIsDrawing;    ...    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    /**     * 初始化     */    private void init() {        // 获取 SurfaceHolder对象        mSurfaceHolder = getHolder();        //注册 SurfaceHolder        mSurfaceHolder.addCallback(this);        // 设置可以获取焦点        setFocusable(true);        // 设置触摸可以获取焦点        setFocusableInTouchMode(true);        // 保持屏幕常亮        this.setKeepScreenOn(true);    }    ...}

     2.3 使用SurfaceView

       使用SurfaceView也可以分为三个步骤:

       1)通过lockCanvas()方法获得Canvas对象。

       2) 在子线程中使用Canvas对象进行绘制。

       3)使用unlockCanvasAndPost()方法将画布内容进行提交。

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

       绘制要充分利用SurfaceView的三个回调方法,在surfaceCreate()方法中开启子线程进行绘制。在子线程中,使用一个while(isDrawing)循环来不停地绘制。具体的绘制过程,由lockCanvas()方法进行绘制,并通过unlockCanvasAndPost(mCanvas)进行画布内容的提交。

       实现代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    private SurfaceHolder mSurfaceHolder;    private Canvas mCanvas;    private boolean mIsDrawing;    public MySurfaceView(Context context) {        this(context, null);    }    public MySurfaceView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    /**     * 初始化     */    private void init() {        // 获取 SurfaceHolder对象        mSurfaceHolder = getHolder();        //注册 SurfaceHolder        mSurfaceHolder.addCallback(this);        // 设置可以获取焦点        setFocusable(true);        // 设置触摸可以获取焦点        setFocusableInTouchMode(true);        // 保持屏幕常亮        this.setKeepScreenOn(true);    }    // SurfaceView 创建时调用    @Override    public void surfaceCreated(SurfaceHolder holder) {        mIsDrawing = true;        // 通过线程池来执行        Executors.newCachedThreadPool().execute(this);    }    // SurfaceView 改变时调用    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    // SurfaceView 销毁时调用    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        mIsDrawing = false;    }    @Override    public void run() {        while (mIsDrawing) {            drawing();        }    }    private void drawing() {        try {            // 获取Canvas 画布            mCanvas = mSurfaceHolder.lockCanvas();            // 下面进行内容的绘制            ...        } finally {            if (mCanvas != null) {                mSurfaceHolder.unlockCanvasAndPost(mCanvas);            }        }    }}

   mSurfaceHolder.unlockCanvasAndPost(mCanvas)将这行代码放入finally代码块中,目的是为了确保内容都能够被提交。

     3. 使用案例

       自定义SurfaceView 实现一个简易画板,初始代码如下所示:

package com.lx.surfaceview.widget;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;import java.util.concurrent.Executors;/** * create by lx * date 2020/8/24. * description: */public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    private SurfaceHolder mSurfaceHolder;    private Canvas mCanvas;    private boolean mIsDrawing;    // 画笔    private Paint mPaint;    // 路径    private Path mPath;    // 上次的坐标    private float mLastX, mLastY;    public MySurfaceView(Context context) {        this(context, null);    }    public MySurfaceView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    /**     * 初始化     */    private void init() {        // 获取 SurfaceHolder对象        mSurfaceHolder = getHolder();        //注册 SurfaceHolder        mSurfaceHolder.addCallback(this);        // 设置可以获取焦点        setFocusable(true);        // 设置触摸可以获取焦点        setFocusableInTouchMode(true);        // 保持屏幕常亮        this.setKeepScreenOn(true);        // 初始化画笔        mPaint = new Paint();        mPaint.setStrokeWidth(10);        mPaint.setColor(Color.BLACK);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setAntiAlias(true);        // 初始化路径        mPath = new Path();    }    // SurfaceView 创建时调用    @Override    public void surfaceCreated(SurfaceHolder holder) {        mIsDrawing = true;        // 通过线程池来执行        Executors.newCachedThreadPool().execute(this);    }    // SurfaceView 改变时调用    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    // SurfaceView 销毁时调用    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        mIsDrawing = false;    }    @Override    public void run() {        while (mIsDrawing) {            drawing();        }    }    private void drawing() {        try {            // 获取Canvas 画布            mCanvas = mSurfaceHolder.lockCanvas();            // 下面进行内容的绘制            mCanvas.drawColor(Color.WHITE);            mCanvas.drawPath(mPath, mPaint);        } finally {            if (mCanvas != null) {                mSurfaceHolder.unlockCanvasAndPost(mCanvas);            }        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        // 获取当前触摸事件的坐标        float x = event.getX();        float y = event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                // 按下                mLastX = x;                mLastY = y;                mPath.moveTo(mLastX, mLastY);                break;            case MotionEvent.ACTION_MOVE:                // 移动                float dx = Math.abs(x - mLastX);                float dy = Math.abs(y - mLastY);                if (dx >= 3 || dy >= 3) {                    mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);                }                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_UP:                // 抬起                break;        }        return true;    }}

       运行效果如下图:

SurfaceView 的基本使用_第1张图片

     4. 优化

       由于 while (isDrawing)是个死循环,drawing方法一直在执行,就导致一直在绘制,这样会一直占用CPU资源。

       1. 使用sleep休眠,在run方法中进行休眠,代码如下:

    public void run() {        while (mIsDrawing) {            long start = System.currentTimeMillis();            drawing();            long end = System.currentTimeMillis();            if (end - start < 100) {                try {                    Thread.sleep(100 - (end - start));                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }

       使用sleep休眠的方式虽然可以降低CPU占用率,但是每次绘制时都会休眠100ms,绘制过程中有种不跟手的感觉。

       2. 使用wait和notify方法,当没有绘制时让线程等待,当有绘制时唤醒线程,代码如下所示:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    ...    private boolean mIsWait = true;      @Override    public void run() {        drawing();        synchronized (MySurfaceView.this) {            while (mIsDrawing) {                if (mIsWait) {                    try {                        MySurfaceView.this.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                drawing();            }        }    }   ...    @Override    public boolean onTouchEvent(MotionEvent event) {        // 获取当前触摸事件的坐标        float x = event.getX();        float y = event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                synchronized (MySurfaceView.this) {                    mIsWait = false;                    MySurfaceView.this.notify();                }                // 按下                mLastX = x;                mLastY = y;                mPath.moveTo(mLastX, mLastY);                break;            case MotionEvent.ACTION_MOVE:                // 移动                float dx = Math.abs(x - mLastX);                float dy = Math.abs(y - mLastY);                if (dx >= 3 || dy >= 3) {                    mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);                }                mLastX = x;                mLastY = y;                break;            case MotionEvent.ACTION_UP:                // 抬起                mIsWait = true;                break;        }        return true;    }}

       手指落下,将mIsWait设置为false表示不需要等待,并唤醒线程。
       手指离开,将mIsWait设置为true表示需要等待。

更多相关文章

  1. android 线程中的ui问题 Handler的基本使用 关于获取动态时间在u
  2. android UI线程向子线程发送Message
  3. Android的子线程能更新UI吗?
  4. Android初始化语言 (init.*.rc、init.conf文件格式)
  5. android 线程大集合
  6. Android开发艺术探索—— 第十一章Android的线程和线程池
  7. 无废话Android之smartimageview使用、android多线程下载、显式意

随机推荐

  1. Android(安卓)数字签名学习笔记
  2. android ndk linux环境搭建
  3. Android(安卓)Studio 第六十八期 - Andro
  4. 传智播客——Android开发(一)Android搭建、
  5. 一个Android健身APP源码(类似KEEP、FEEL、
  6. Android(安卓)Tab切换之Fragment方法
  7. 转 Android(安卓)SDK Manager国内无法更
  8. android实现显示阳历和农历源码
  9. Android计时器TimerTask,Timer,Handler
  10. Android通信之 Bluetooth