系列文章目錄
講講Android為自定義view提供的SurfaceView
文章目錄
- 系列文章目錄
- 前言
- 一、Android為什麼會提供SurfaceView
- 二、先看看Android Demo的實作
-
- 1.實作接口以及接口定義的方法
- 2.與Activity生命周期進行綁定
- 3.完成初始化操作
- 4.實作
- 5.運作
- 三、繼承SurfaceView實作
-
- 1.自定義類繼承自SurfaceView,并且實作兩個接口以及接口定義的方法。
- 2.初始化
- 3.步驟與Android Demo的實作-4.實作類似
- 四、放一個使用案例源碼
- 五、拓展一下(以下内容來源于網絡)
前言
前幾天發表了幾篇在自定義view中通過修改值實作動态效果的文章。起到主要作用的是調用重新整理界面的方法。但是假設繪制的過程邏輯比較複雜,并且界面更新頻繁,這時候就會造成界面的卡頓。十分影響使用者體驗感。
靈感來源于,Android官方demo(效果圖如下)

一、Android為什麼會提供SurfaceView
View是通過重新整理來重繪視圖,并且有一個重新整理的間隔,當繪制過程邏輯很複雜加上界面更新還非常頻繁時,就可能無法在間隔内完成繪制,就會造成界面效果的卡頓,影響使用者體驗,為此Android提供了SurfaceView來解決這一問題。
二、先看看Android Demo的實作
1.實作接口以及接口定義的方法
public void surfaceCreated(SurfaceHolder holder) {
synchronized (mDrawingThread) {
mDrawingThread.mSurface = holder;
mDrawingThread.notify();
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//這裡不需要做任何事情;繪制線程将從畫布中擷取
}
public void surfaceRedrawNeeded(SurfaceHolder holder) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
//我們需要告訴繪圖線程停止
synchronized (mDrawingThread) {
mDrawingThread.mSurface = holder;
mDrawingThread.notify();
while (mDrawingThread.mActive) {
try {
mDrawingThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.與Activity生命周期進行綁定
@Override
protected void onPause() {
super.onPause();
//當我們暫停時,確定繪制線程沒有運作。
synchronized (mDrawingThread) {
mDrawingThread.mRunning = false;
mDrawingThread.notify();
}
}
@Override
protected void onResume() {
super.onResume();
//讓繪圖線程繼續運作。
synchronized (mDrawingThread) {
mDrawingThread.mRunning = true;
mDrawingThread.notify();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//確定繪圖線程消失。
synchronized (mDrawingThread) {
mDrawingThread.mQuit = true;
mDrawingThread.notify();
}
}
3.完成初始化操作
4.實作
- 通過
方法獲得Canvas對象lockCanvas()
//鎖定畫布進行繪圖。
Canvas canvas = mSurface.lockCanvas();
if (canvas == null) {
Log.i("WindowSurface", "Failure locking canvas");
continue;
}
- 在子線程中使用Canvas對象進行繪制
// 更新圖形
if (!mInitialized) {
mInitialized = true;
mPoint1.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
mPoint2.init(canvas.getWidth(), canvas.getHeight(), mMinStep);
mColor.init(127, 127, 1);
} else {
mPoint1.step(canvas.getWidth(), canvas.getHeight(),
mMinStep, mMaxStep);
mPoint2.step(canvas.getWidth(), canvas.getHeight(),
mMinStep, mMaxStep);
mColor.step(127, 127, 1, 3);
}
//顔色的效果
mBrightLine+=2;
if (mBrightLine > (NUM_OLD*2)) {
mBrightLine = -2;
}
// 清理背景
canvas.drawColor(mBackground.getColor());
// 畫舊線
for (int i=mNumOld-1; i>=0; i--) {
mForeground.setColor(mOldColor[i] | makeGreen(i));
mForeground.setAlpha(((NUM_OLD-i) * 255) / NUM_OLD);
int p = i*4;
canvas.drawLine(mOld[p], mOld[p+1], mOld[p+2], mOld[p+3], mForeground);
}
// 畫新線
int red = (int)mColor.x + 128;
if (red > 255) red = 255;
int blue = (int)mColor.y + 128;
if (blue > 255) blue = 255;
int color = 0xff000000 | (red<<16) | blue;
mForeground.setColor(color | makeGreen(-2));
canvas.drawLine(mPoint1.x, mPoint1.y, mPoint2.x, mPoint2.y, mForeground);
// 添加新的線條
if (mNumOld > 1) {
System.arraycopy(mOld, 0, mOld, 4, (mNumOld-1)*4);
System.arraycopy(mOldColor, 0, mOldColor, 1, mNumOld-1);
}
if (mNumOld < NUM_OLD) mNumOld++;
mOld[0] = mPoint1.x;
mOld[1] = mPoint1.y;
mOld[2] = mPoint2.x;
mOld[3] = mPoint2.y;
mOldColor[0] = color;
- 使用
方法将畫布内容進行送出unlockCanvasAndPost()
//全部完成
mSurface.unlockCanvasAndPost(canvas);
5.運作
//告訴活動的視窗,我們想做我們自己的繪制
getWindow().takeSurface(this);
//這是将繪制到我們的表面的線程。
mDrawingThread = new DrawingThread();
mDrawingThread.start();
三、繼承SurfaceView實作
1.自定義類繼承自SurfaceView,并且實作兩個接口以及接口定義的方法。
public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//建立
@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.初始化
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
......
3.步驟與Android Demo的實作-4.實作類似
- 通過
方法獲得Canvas對象lockCanvas()
- 在子線程中使用Canvas對象進行繪制(run())
- 使用
方法将畫布内容進行送出unlockCanvasAndPost()
try {
mCanvas = mSurfaceHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
//繪制的邏輯
.....
}catch (Exception e){
}finally {
if (mCanvas != null){
//釋放canvas對象并送出畫布
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
四、放一個使用案例源碼
效果見:Android自定義view之線條等待動畫(靈感來源:金鏟鏟之戰)
public class MyView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mSurfaceHolder;
private Canvas mCanvas;
private int mWidth;
private int mHeight;
private int useWidth, minwidth;
private boolean viewContinue=true,viewContinue1=true;
private float mSweep,mSweep1;
private boolean runDrawing;
private Paint mPaint;
public MyView(Context context) {
super(context);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//建立
@Override
public void surfaceCreated(SurfaceHolder holder) {
runDrawing = true;
new Thread(this).start();
}
//改變
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//銷毀
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
runDrawing=false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
useWidth = mWidth;
if (mWidth > mHeight) {
useWidth = mHeight;
}
}
//子線程
@Override
public void run() {
while (runDrawing){
draw();
}
}
//繪制
private void draw() {
try {
//獲得canvas對象
mCanvas = mSurfaceHolder.lockCanvas();
//繪制背景顔色
mCanvas.drawColor(Color.WHITE);
minwidth = useWidth / 10;
mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5+mSweep,mPaint);
mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5-mSweep,mPaint);
mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5+mSweep,minwidth*5-mSweep,mPaint);
mCanvas.drawLine(minwidth*5,minwidth*5,minwidth*5-mSweep,minwidth*5+mSweep,mPaint);
mCanvas.drawLine(minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep,minwidth*5+mSweep-mSweep1,mPaint);
mCanvas.drawLine(minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep,minwidth*5-mSweep+mSweep1,mPaint);
mCanvas.drawLine(minwidth*5+mSweep,minwidth*5-mSweep,minwidth*5+mSweep-mSweep1,minwidth*5-mSweep,mPaint);
mCanvas.drawLine(minwidth*5-mSweep,minwidth*5+mSweep,minwidth*5-mSweep+mSweep1,minwidth*5+mSweep,mPaint);
if (viewContinue&&viewContinue1){
mSweep += 2;
if (mSweep > minwidth*2) {
viewContinue=false;
}
}
if (!viewContinue&&viewContinue1){
mSweep1 += 4;
if (mSweep1 > 4*minwidth) {
viewContinue1=false;
viewContinue=true;
}
}
if (viewContinue&&!viewContinue1){
if (mSweep1 <=0) {
mSweep-=4;
if (mSweep<0){
viewContinue=true;
viewContinue1=true;
}
}else{
mSweep1 -= 2;
}
}
//重新整理View
invalidate();
}catch (Exception e){
}finally {
if (mCanvas != null){
//釋放canvas對象并送出畫布
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
private void initView(){
mSurfaceHolder = getHolder();
//注冊回調方法
mSurfaceHolder.addCallback(this);
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
//初始化畫筆
initPaint();
}
private void initPaint() {
mPaint = new Paint(); //建立畫筆對象
mPaint.setColor(Color.BLACK); //設定畫筆顔色
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4f); //設定畫筆寬度為10px
mPaint.setAntiAlias(true); //設定抗鋸齒
mPaint.setAlpha(255); //設定畫筆透明度
}
}
五、拓展一下(以下内容來源于網絡)
View和SurfaceView的差別:
- View适用于主動更新的情況,而SurfaceView則适用于被動更新的情況,比如頻繁重新整理界面。
- View在主線程中對頁面進行重新整理,而SurfaceView則開啟一個子線程來對頁面進行重新整理。
- View在繪圖時沒有實作雙緩沖機制,SurfaceView在底層機制中就實作了雙緩沖機制。