SurfaceView的简单使用

SurfaceView

1、SurfaceView的基本介绍

SurfaceView 是继承自View的,属于View的一个小小的分枝。

  • View 通过onDraw()方法里面的Canvas对象去绘制自己到屏幕上面。
  • SurfaceView 并不需要实现OnDraw()方法。那么我们的Surface是如何绘制自身的呢!其实View是在UI线程当中绘制自己,而SurfaceView是在子线程当中绘制自身。就是因为在子线程当中绘制自己所以不管绘制是多么的缓慢,还是绘制方法多么频繁的调用,其都不会阻塞UI线程。所以像播放视频时候使用的View,还是游戏所使用的View都是SurfaceView,而不是View。
  • SurfaceView是在子线程当中进行绘制,那么它是如何获取到Canvas的,如何进行绘制的?

    答:在SurfaceView当中存在一个Surface,而在Surface当中持有Canvas,但是我们拿不到Surface,系统为之暴露了一个SurfaceHolder(Surface的持有者),那么我们可以通过SurfaceView获取SurfaceHolder,然后通过SurfaceHolder获取Canvas。

    1
    2
    SurfaceHolder holder = new SurfaceView().getHolder();
    Canvas canvas = holder.getCanvas();
  • SurfaceView的绘制时机是什么时候,我们知道View的绘制时机是在onDraw()方法里面,但是SurfaceView的绘制时机在什么时候。==

    答:之前说过的SurfaceHolder不仅仅能获取Canvas对象,其实SurfaceHolder还管理者SurfaceView的生命周期。同时生命周期如下所示:

    • SurfaceCreated():创建相应的子线程,然后在子线程的run()方法当中开启对SurfaceView的绘制
    • SurfaceChanged():相当与对SurfaceView的进行监听。
    • SurfaceDestorye():关闭我们子线程,以及对一些资源的回收。

2、SurfaceView的基本写法

常见的SurfaceView的写法,这里是模版代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

/**
* SurfaceView的自定义流程
* Created by pengchengliu on 2017/8/25.
*/

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

private SurfaceHolder mHolder ;
private Thread mThread ;
private Canvas mCanvas ;
private boolean isRunning ;

public SurfaceDemo(Context context) {
this(context, null);
}
public SurfaceDemo(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceDemo(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onFinishInflate() {
mHolder = this.getHolder();
this.setFocusable(true); //设置SurfaceView为可以获取焦点。
this.setFocusableInTouchMode(true); //设置SurfaceView可以触摸。
this.setKeepScreenOn(true); //设置SurfaceView的屏幕应该为保持高亮。
mHolder.addCallback(this);
super.onFinishInflate();
}

/**
* 创建相应的子线程,然后在子线程的run()方法当中开启对SurfaceView的绘制
* @param holder 当前SurfaceView的Surface的持有者。
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
mThread = new Thread(this);
isRunning = true ;
mThread.start();
}

/**
*:相当与对SurfaceView的进行监听。
* 任何结构变更后立即调用(格式或尺寸)已经制成表面。 应该在这一点上更新表面的图像。
* 这种方法总是至少被调用一次,{@link #surfaceCreated}之后。
* @param holder 表面已更改的SurfaceHolder。
* @param format 式表面的新的PixelFormat。
* @param width 表面的新宽度。
* @param height 表面的新高度。
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

/**
* 当SurfaceView被销毁的时候进行回调。
* @param holder 当前SurfaceView的Surface的持有者,也就是SurfaceHolder
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false ;
}

@Override
public void run() {
while (isRunning){
draw();
}
}

/**
* 进行绘制操作。主要思想就是得到绘制工具Canvas,然后在不必要的情况下进行Canvas的释放
*/
private void draw() {
try {
//获取Canvas对象
mCanvas = mHolder.lockCanvas();
//TODO 为什么进行判空操作呢?
// 主要原因就是由于这个方法是在不断进行调用,所以当用户点击Home或者回退按键的话,当前的SurfaceView对象就会被销毁,
// 相应的holder对象也会被销毁,所以在这里通过hodler获取到的canvas也就是空的。所以进行判空操作。
if(mCanvas != null){
//进行绘制
}
//TODO 为什么进行try-catch呢?
// 因为就是当前页面如果被销毁掉,也就是说当前的SurfaceView已经被销毁,但是线程不会很快就被销毁,所以在这个地方就会抛出各种各样的异常。
// 在这里是try-catch,就是防止上述情况下写出来的。
} catch (Exception e) {} finally {
if(mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}