前言
在寫 SurfaceView之前当悔,先來記錄下 自定義View区丑,然后再引出 SurfaceView划栓;
1. 自定義View
針對于自定義View茧彤,我們需要知道以下幾點:
- 自定義View 一般用來實現(xiàn)一些動畫效果常侦,是通過不斷執(zhí)行 View.onDraw()方法洋机,比如 onDraw()方法每秒執(zhí)行20次坠宴,會形成一個20幀的補間動畫,這里涉及到幀數(shù)的概念绷旗;
- 幀數(shù):就是每秒onDraw()方法執(zhí)行的次數(shù)喜鼓;
- onDraw()方法是系統(tǒng)幫我們調(diào)用,我們通過調(diào)用系統(tǒng)的 invalidate()方法 通知系統(tǒng)需要重新繪制 View衔肢,然后它就會調(diào)用 View.onDraw()方法庄岖,這些都是系統(tǒng)幫我們調(diào)用的;
基于以上角骤,我們很難準確定義 onDraw()方法的執(zhí)行幀數(shù)隅忿,這個時候就需要引入 SurfaceView,來彌補 自定義View的一些不足之處邦尊,下邊先來看一下我用 自定義View實現(xiàn)的一個動畫效果背桐,效果是從中間擴散到最大的圓,效果圖及代碼如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/5/7 13:25
* Version 1.0
* Params:
* Description: 自定義View - 實現(xiàn)動畫效果
*/
public class AnimateViewActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 這邊傳入的this代表這個對象蝉揍,因為Activity是繼承自Content類的链峭,因此該對象也
// 可向上轉(zhuǎn)型為Content類型作為AnimateView的構(gòu)造方法的參數(shù)
setContentView(new AnimateView(this));
}
class AnimateView extends View {
float radius = 10;
Paint paint;
public AnimateView(Context context) {
super(context);
paint = new Paint();
paint.setColor(Color.GREEN); // 圓圈顏色
paint.setStyle(Paint.Style.STROKE); // 線條模式 文字、圖像只顯示邊界
paint.setStrokeWidth((float) 10.0); // 寬度
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(200, 200);
canvas.drawCircle(0, 0, radius++, paint);
if(radius > 100){
radius = 10;
}
invalidate();//通過調(diào)用這個方法讓系統(tǒng)自動刷新視圖
}
}
}
2. 對自定義 View實現(xiàn)的動畫效果總結(jié)
- 因為自定義View的幀數(shù)是有系統(tǒng)控制的又沾,所以采用自定義View 實現(xiàn)的動畫不能控制 動畫執(zhí)行的速度弊仪;
- 可以把自定義View理解為:經(jīng)過系統(tǒng)優(yōu)化熙卡、可以高效執(zhí)行的幀數(shù)比較低的動畫效果,也就是說它有其具體的使用場景励饵,比如一些幀數(shù)比較低的游戲:貪吃蛇驳癌、棋牌類、俄羅斯方塊等
這個時候我們就需要一些幀數(shù)比較快的曲横、可以自己控制幀數(shù)的對象喂柒,因此SurfaceView就橫空出世了;
3. SurfaceView介紹
- 可以控制幀數(shù)禾嫉;
- 自定義VIew的更新UI灾杰,只能放在主線程中,SurfaceView可以讓其他線程更新UI熙参,根據(jù)這個特性艳吠,就可以控制它的幀數(shù),如果讓這個線程1秒執(zhí)行50次孽椰,最后就是顯示50幀
4. SurfaceView具體步驟如下
1>:SurfaceView也是一個View昭娩,它有自己的生命周期,surfaceCreate()黍匾、surfaceChange()栏渺、surfaceDestroy()這3個方法;
2>:可以在自定義DemoSurfaceView的構(gòu)造方法中開一個 LoopThread線程锐涯,然后進行繪制自定義View磕诊;
3>:在生命周期結(jié)束時,也就是 surfaceDestroy()方法中結(jié)束線程纹腌;
其實上邊這些都是由 其SurfaceHolder對象完成的霎终。SurfaceHolder保存了Surface對象的引用SurfaceHolder生命周期就是 Surface的生命周期,使用SurfaceHolder處理生命周期的初始化升薯;
具體代碼如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/5/7 13:40
* Version 1.0
* Params:
* Description: 自定義的SurfaceView - 用于實現(xiàn)動畫效果
*/
public class DemoSurfaceView extends SurfaceView implements Callback{
LoopThread thread;
public DemoSurfaceView(Context context) {
super(context);
//初始化,設置生命周期回調(diào)方法
init();
}
public DemoSurfaceView(Context context,AttributeSet attrs){
super(context , attrs);
//初始化,設置生命周期回調(diào)方法
init();
}
private void init(){
SurfaceHolder holder = getHolder();
//設置Surface生命周期回調(diào)
holder.addCallback(this);
// LoopThread線程用于繪制圖形
thread = new LoopThread(holder, getContext());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.isRunning = true;
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 注意3:通過標志位關(guān)閉LoopThread線程
thread.isRunning = false;
try {
// 關(guān)閉LoopThread線程莱褒,這里使用join()方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 執(zhí)行繪制的繪制線程
*/
class LoopThread extends Thread{
SurfaceHolder surfaceHolder;
Context context;
boolean isRunning;
float radius = 10f;
Paint paint;
public LoopThread(SurfaceHolder surfaceHolder,Context context){
this.surfaceHolder = surfaceHolder;
this.context = context;
isRunning = false;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth((float) 10.0); // 寬度
}
@Override
public void run() {
Canvas c = null;
while(isRunning){
try{
// 注意1:同步鎖為了防止多個線程同時操作同一個Canvas的c
synchronized (surfaceHolder) {
c = surfaceHolder.lockCanvas(null);
doDraw(c);
//通過它來控制幀數(shù)執(zhí)行一次繪制后休息50ms
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
public void doDraw(Canvas c){
// 注意2:SurfaceView特點:會保留之前繪制的圖像,這里需要首先清空上一次繪制的圖形
// 自定義View不用手動清理涎劈,自定義View在調(diào)用onDraw()方法就已經(jīng)自動清除掉視圖中的東西
c.drawColor(Color.BLACK);
c.translate(200, 200);
c.drawCircle(0,0, radius++, paint);
if(radius > 100){
radius = 10f;
}
}
}
}
上邊代碼需要注意:
1>:為防止多個線程同時操作 同一個 Canvas對象广凸,需要添加同步鎖synchronize,并且在操作完成后需要釋放Canvas鎖责语;
2>:在調(diào)用 doDraw()執(zhí)行繪制時候炮障,因為 SurfaceView的特點,它會保留之前繪制的圖形坤候,需要先清空上一次繪制時留下的圖形胁赢;
3>:在surfaceDestroy()方法中關(guān)閉線程就ok;
5. View與SurfaceView的區(qū)別白筹?
1>:自定義View只能在主線程中 更新UI智末,不能再子線程中更新谅摄,而SurfaceView可以在任何線程中更新UI;
2>:SurfaceView可以控制幀數(shù)系馆,執(zhí)行動畫效率比View的高送漠;
3>:SurfaceView是放在最底層,可以在它上邊添加一些層由蘑,而且不能是透明的闽寡;
4>:SurfaceView定義和使用比自定義View復雜,占用資源比較多尼酿,一般情況用自定義View就行爷狈,實在不行,最后再考慮SurfaceView裳擎;
具體代碼已上傳至github:
https://github.com/shuai999/SurfaceViewDemo.git