-
為什么要用SurfaceView
在自定義View中,我們知道使用onDraw可以做一些簡單的動畫效果。通過不斷循環的執行View.onDraw方法,每次執行都對內部顯示的圖形做一些調整,我們假設onDraw方法每秒執行20次,這樣就會形成一個20幀的補間動畫效果。但是現實情況是你無法簡單的控制View.onDraw的執行幀數,這邊說的幀數是指每秒View.onDraw方法被執行多少次,這是為什么呢?首先我們知道,onDraw方法是由系統幫我們調用的,我們是通過調用View的invalidate方法通知系統需要重新繪制View,然后它就會調用View.onDraw方法。這些都是由系統幫我們實現的,所以我們很難精確去定 義View.onDraw的執行幀數,這個就是為什么我們這邊要了解SurfaceView了,它能彌補View的一些不足。
-
SurfaceView與View有什么不同?
對于一個View的onDraw方法,不能從后臺線程修改一個GUI元素。
當需要快速地更新View的UI,或者當渲染代碼阻塞GUI線程的時間過長的時候,SurfaceView就是解決上述問題的最佳選擇。 SurfaceView封裝了一個Surface對象,而不是Canvas。這一點很重要,因為Surface可以使用后臺線程繪制。對于那些資源敏感的操作,或者那些要求快速更新或者高速幀率的地方,例如,使用3D圖形,創建游戲,或者實時預覽攝像頭
-
讓我們來看一看Google文檔
Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen
The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed. The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. This can be used to place overlays such as buttons on top of the Surface, though note however that it can have an impact on performance since a full alpha-blended composite will be performed each time the Surface changes.
The transparent region that makes the surface visible is based on the layout positions in the view hierarchy. If the post-layout transform properties are used to draw a sibling view on top of the SurfaceView, the view may not be properly composited with the surface.
Access to the underlying surface is provided via the SurfaceHolder interface, which can be retrieved by calling getHolder().
The Surface will be created for you while the SurfaceView's window is visible; you should implement surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder) to discover when the Surface is created and destroyed as the window is shown and hidden.
One of the purposes of this class is to provide a surface in which a secondary thread can render into the screen. If you are going to use it this way, you need to be aware of some threading semantics:
All SurfaceView and SurfaceHolder.Callback methods will be called from the thread running the SurfaceView's window (typically the main thread of the application). They thus need to correctly synchronize with any state that is also touched by the drawing thread. You must ensure that the drawing thread only touches the underlying Surface while it is valid -- between SurfaceHolder.Callback.surfaceCreated() and SurfaceHolder.Callback.surfaceDestroyed().
這段話的意思是:
SurfaceView是視圖(View)的繼承類,這個視圖里內嵌了一個專門用于繪制的Surface。你可以控制這個Surface的格式和尺寸。Surfaceview控制這個Surface的繪制位置。
surface是縱深排序(Z-ordered)的,這表明它總在自己所在窗口的后面。surfaceview提供了一個可見區域,只有在這個可見區域內的surface部分內容才可見,可見區域外的部分不可見。surface的排版顯示受到視圖層級關系的影響,它的兄弟視圖結點會在頂端顯示。這意味者surface的內容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控件)。注意,如果surface上面 有透明控件,那么它的每次變化都會引起框架重新計算它和頂層控件的透明效果,這會影響性能。
你可以通過SurfaceHolder接口訪問這個surface,getHolder()方法可以得到這個接口。
surfaceview變得可見時,surface被創建;surfaceview隱藏前,surface被銷毀。這樣能節省資源。如果你要查看 surface被創建和銷毀的時機,可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
surfaceview的核心在于提供了兩個線程:UI線程和渲染線程。這里應注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI線程里調用,一般來說就是應用程序主線程。渲染線程所要訪問的各種變量應該作同步處理。
2> 由于surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效的surface。
通過谷歌官方的介紹,我們初步了解了SurfaceView的使用方法,但是,具體怎么使用,不如我們敲代碼來的深刻。
-
Demo
我們可以寫一個自定義SurfaceView,不在UI線程中更新UI顯示的例子,通過畫圓圈,使得圓圈不斷變大變小的動畫來做案例。
首先,我們寫一個MySurfaceView 繼承于SurfaceView 且完成Callback接口
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
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() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
然后我們添加一個線程,在線程中,拿到SurfaceHolder 進行繪制,同時我們打印UI線程號和draw時候的線程號。
總體代碼如下,
/**
* Created by Xiamin on 2016/11/26.
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private DrawThread mThread;
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() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("iii", "surfaceCreated");
mThread = new DrawThread(getContext(), holder);
mThread.start();
Log.i("iii", "UI thread id: " + Thread.currentThread().getId());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mThread.stopThread();
}
class DrawThread extends Thread {
SurfaceHolder surfaceHolder;
Context context;
Paint paint;
private boolean isRunning = true;
float r = 10;
float diff = 0;
public DrawThread(Context context, SurfaceHolder holder) {
this.context = context;
this.surfaceHolder = holder;
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
}
@Override
public void run() {
while (isRunning) {
synchronized (surfaceHolder) {
if (surfaceHolder != null) {
Canvas canvas = surfaceHolder.lockCanvas();
draw(canvas);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public void draw(Canvas canvas) {
if (r < 30) {
diff = 10;
} else if (r > 150) {
diff = -10;
}
r += diff;
canvas.drawColor(Color.WHITE);
canvas.translate(200, 200);
canvas.drawCircle(0, 0, r, paint);
Log.i("iii", "draw thread id: " + Thread.currentThread().getId());
}
private void stopThread() {
Log.i("iii", "stopThread()");
isRunning = false;
}
}
}
執行效果如圖
Log日志為:
11-27 05:06:10.979 2752-2752/com.example.surfaceviewdemo I/iii: surfaceCreated
11-27 05:06:10.979 2752-2752/com.example.surfaceviewdemo I/iii: UI thread id: 1
11-27 05:06:11.027 2752-2767/com.example.surfaceviewdemo I/iii: draw thread id: 107
11-27 05:06:11.231 2752-2767/com.example.surfaceviewdemo I/iii: draw thread id: 107
11-27 05:06:11.343 2752-2767/com.example.surfaceviewdemo I/iii: draw thread id: 107
11-27 05:06:11.439 2752-2767/com.example.surfaceviewdemo I/iii: draw thread id: 107
可以看出,我們的繪圖線程與UI線程不是同一個線程。這帶來的好處是巨大的。
謝謝大家閱讀,如有幫助,來個喜歡或者關注吧!
本文作者:Anderson/Jerey_Jobs
簡書地址:Anderson大碼渣
github地址:Jerey_Jobs