SurfaceView理解
surface可以這樣理解:它是內(nèi)存中一塊區(qū)域囊拜,它是surfaceview不可見那個部分苗胀,繪圖操作作用于它,然后它就會被顯卡之類的顯示控制器繪制到屏幕上团秽。
surface是個啥疯趟,大概已經(jīng)有了些概念了。因為它對應(yīng)了一個內(nèi)存區(qū)锡溯,大家都知道赶舆,內(nèi)存區(qū)的對象是有生命周期的,可以動態(tài)的申請創(chuàng)建和銷毀祭饭,當(dāng)然也可能會更新芜茵。于是,就有了作用于這個內(nèi)存區(qū)的操作倡蝙,這些操作就是surfaceCreated/Changed/Destroyed九串。三個操作放在一起,就是callback,
所以在很多例子里看到,會有callback猪钮。
SurfaceView 與 View的比較
- 雙緩沖
- View適用主動更新品山,SurfaceView 適用被動更新,如頻繁的刷新
- View主線程 SurfaceView子線程刷新(需要界面迅速更新烤低、對幀率要求較高的情況)
雙緩沖
在運用時可以理解為:SurfaceView在更新視圖時用到了兩張Canvas肘交,一張frontCanvas和一張backCanvas,每次實際顯示的是frontCanvas拂玻,backCanvas存儲的是上一次更改前的視圖酸些,當(dāng)使用lockCanvas()獲取畫布時,得到的實際上是backCanvas而不是正在顯示的frontCanvas檐蚜,之后你在獲取到的backCanvas上繪制新視圖魄懂,再unlockCanvasAndPost(canvas)此視圖,那么上傳的這張canvas將替換原來的frontCanvas作為新的frontCanvas闯第,原來的frontCanvas將切換到后臺作為backCanvas市栗。例如,如果你已經(jīng)先后兩次繪制了視圖A和B咳短,那么你調(diào)用lockCanvas()獲取視圖填帽,獲得的將是A而不是正在顯示的B,之后你講重繪的C視圖上傳咙好,那么C將取代B作為新的frontCanvas顯示在SurfaceView上篡腌,原來的B則轉(zhuǎn)換為backCanvas。
SurfaceView模板
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class SurfaceViewTemplate extends SurfaceView
implements SurfaceHolder.Callback, Runnable {
// SurfaceHolder
private SurfaceHolder mHolder;
// 用于繪圖的Canvas
private Canvas mCanvas;
// 子線程標(biāo)志位
private boolean mIsDrawing;
public SurfaceViewTemplate(Context context) {
super(context);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
//mHolder.setFormat(PixelFormat.OPAQUE);
}
@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 sth
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
原理分析
-
SurfaceView的繪圖表面的創(chuàng)建過程
由于SurfaceView具有獨立的繪圖表面勾效,因此嘹悼,在它的UI內(nèi)容可以繪制之前,我們首先要將它的繪圖表面創(chuàng)建出來层宫。盡管SurfaceView不與它的宿主窗口共享同一個繪圖表面杨伙,但是它仍然是屬于宿主窗口的視圖結(jié)構(gòu)的一個結(jié)點的,也就是說萌腿,SurfaceView仍然是會參與到宿主窗口的某些執(zhí)行流程中去限匣。
SurfaceView 示例
1.畫正弦函數(shù)
思路很簡單:
初始化一個Path,每次draw函數(shù)都會重新計算x,y的值毁菱,通過Path的moveTo方法米死,然后通過Canvas的drawPath得到曲線。
public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean mIsDrawing;
private int x = 0;
private int y = 0;
private Path mPath;
private Paint mPaint;
public SinView(Context context) {
super(context);
initView();
}
public SinView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SinView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
mPath.moveTo(0, 400);
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();
x += 3;
y = (int) (100*Math.sin(x * Math.PI / 180) + 400);
mPath.lineTo(x, y);
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
// SurfaceView背景
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
//使用
setContentView(new SinView(this));
很簡單贮庞,在SurfaceView模板基礎(chǔ)上增加了Path和x,y的控制哲身!
2.按照Path Segment 加入動畫繪制
這個示例的View不是繼承SurfaceView
我們只看第二行第一個如何繪制的,其他都一樣
public class PathView1 extends View {
//一個開源類贸伐,原理后面講
PathAnimator mPathAnimator;
int w,h;
Path mPath;
Paint mPaint = new Paint();
public PathView1(Context context) {
super(context);
}
public PathView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PathView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
init();
super.onSizeChanged(w, h, oldw, oldh);
}
private void init(){
//畫筆顏色
mPaint.setStrokeWidth(20);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.GREEN);
mPaint.setAntiAlias(true);
//要繪制的路徑
Path path = new Path();
path.addCircle(w / 2, h / 2, w / 2 - 20, Path.Direction.CCW);
path.moveTo(w / 2 - 60, h / 2 + 10);
path.lineTo(w / 2 - 30, h / 2 + 50);
path.lineTo(w / 2 + 50, h / 2 - 60);
//路徑動畫
mPathAnimator = new PathAnimator(path);
mPathAnimator.setDuration(3000);//動畫時間
mPathAnimator.startDelay(1000);
mPathAnimator.addUpdateListener(new PathAnimator.PathAnimatorUpdateListener() {
@Override
public void onAnimationUpdate(float pathPrecent, Path path) {
mPath = path;//更新當(dāng)前路徑
invalidate();
}
});
mPathAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
if(mPath!=null){
canvas.drawPath(mPath,mPaint);
}
}
}
PathAnimator 原理
- 通過構(gòu)造方法勘天,傳入一個Path進去。
public PathAnimator(Path path){
mPath = path;
mPathMeasure = new PathMeasure(mPath,false);
/**
* 初始化路徑
* 將復(fù)合路徑分割保存到list.
* 記錄路徑長度
*/
initPath();
//路徑動畫,能夠根據(jù)當(dāng)前時間脯丝,計算出當(dāng)前運動路徑
initAnim();
}
具體源碼請看:https://github.com/jacky1234/PathAnimator