Android:SurfaceView 的使用(附代码模板)

前言

摘自《Android群英传》

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 在主线程中对画面进行刷新,而 SurfaceView 通常会通过一个子线程来进行页面的刷新。
  • View 在绘图时没有使用双缓冲机制,而 SurfaceView 在底层实现机制中就已经实现了双缓冲机制。
    总结就是,如果你的自定义View需要频繁刷新,或者刷新时数据处理量比较大,那么你就可以考虑使用 SurfaceView 来取代 View 了。

SurfaceView 的使用

SurfaceView 的使用虽然比 View 复杂,但是 SurfaceView 在使用时,有一套使用的模板代码,大部分的 SurfaceView 绘图操作都可以套用这样的模板代码来进行编写。因此 SurfaceView 的使用会更加简单。

创建一个 SurfaceView 的模板
1、 创建 SurfaceView

创建自定义的 MySurfaceView 继承自 SurfaceView ,并实现两个接口——SurfaceHolder.Callback, Runnable,同时实现其接口方法,如下:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    @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() {
    }
}
2、初始化 SurfaceView

在自定义 SurfaceView 的构造方法中,需要对 SurfaceView 进行初始化。通常需要定义以下三个成员变量,如下:

private SurfaceHolder mHolder;
//用于绘图的canvas
private Canvas mCanvas;
//子线程标志位
private boolean mIsDrawing;

初始化方法就是初始化一个 SurfaceHolder 对象并注册 SurfaceHolder 的回调方法,如下:

mHolder = getHolder();
mHolder.addCallback(this);

另外两个成员变量——Canvas 和标志位。使用 Canvas 来进行绘图;使用标志位来控制之前提到的用于绘制的子线程。

3、使用 SurfaceView

通过 SurfaceHolder 对象的 lockCanvas() 方法就可以获得当前的 Canvas 绘图对象。接下来就可以与在 View 中进行的绘制操作一样进行绘制了。这里需要注意,获取到的 Canvas 对象还是继续上次的 Canvas 对象,而不是一个新的 Canvas 对象。因此,之前的绘图操作都会被保留。如果需要擦除,则可以在绘制前,通过 drawColor() 方法来进行清屏操作。

绘制时,充分利用 SurfaceView 的三个回调方法,在 surfaceCreated() 方法里开启子线程进行绘制,而子线程使用一个 while (mIsDrawing) {} 的循环来不停地进行绘制,而在绘制的具体逻辑中,通过 lockCanvas() 方法来获取 Canvas 对象来绘制,并通过 unlockCanvasAndPost(mCanvas) 方法对画布内容进行提交。整个 SurfaceView 模板代码如下:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * Created by Deeson on 2017/5/23.
 */
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    public MySurfaceView (Context context) {
        super(context);
        init();
    }

    public MySurfaceView (Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MySurfaceView (Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }

    @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 something
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}

以上代码基本可以满足大部分 SurfaceView 的绘图需求,唯一需要注意的是在绘制方法中,将 mHolder.unlockCanvasAndPost(mCanvas);方法放到 finally 代码块中,保证每次都能将内容提交。

最后

关于 SurfaceView 的实例演练,可以看看我的这篇文章 Android:贝塞尔曲线原理分析

按照惯例,需要送上 demo 下载,如下 gif 所示:

mySurfaceView.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容