SurfaceView 基本使用

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;
    }
}

       运行效果如下图:

     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 - drawing时间),绘制过程中有种不跟手的感觉。

       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表示需要等待。

猜你喜欢

转载自blog.csdn.net/lixiong0713/article/details/108194954