Android繪圖機制與處理技巧(五)——View的孿生兄弟SurfaceView
阿新 • • 發佈:2019-02-14
SurfaceView與View的區別
View通過重新整理來重繪檢視,Android系統通過發出VSYNC訊號來進行螢幕的重繪,重新整理的間隔時間為16ms。如果在16ms內View完成了所需要執行的所有操作,那麼在使用者的視覺上,就不會產生卡頓的感覺;而如果執行的操作邏輯太多,特別是需要頻繁重新整理的介面上,例如遊戲介面,那麼就會不斷阻塞主執行緒,從而導致畫面卡頓。很多時候,在自定義View的Log中經常會看到如下所示的警告。
“Skipped 47 frames! The application may be doing too much work on its main thread”
這些警告的產生,很多情況下就是因為在繪製過程中,處理邏輯太多造成的。
為了避免這一問題的產生,Android系統提供了SurfaceView元件來解決這個問題。SurfaceView可以說是View的孿生兄弟,但它與View還是有所不同,它們的區別主要體現在以下幾點。
- View主要適用於主動更新的情況下,而SurfaceView主要適用於被動更新,例如頻繁地重新整理。
- View在主執行緒中對畫面進行重新整理,而SurfaceView通常會通過一個子執行緒來進行頁面的重新整理。
- View在繪圖時沒有使用雙緩衝機制,而SurfaceView在底層實現機制中就已經實現了雙緩衝機制。
總之,如果你的自定義View需要頻繁重新整理,或者重新整理時資料處理量比較大,那麼你就可以考慮使用SurfaceView來取代View了。
SurfaceView的使用
使用SurfaceView的模板程式碼:
/**
* Created by Administrator on 2016/6/1.
* 使用SurfaceView的模板程式碼
*/
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
// SurfaceHolder
private SurfaceHolder mHolder;
// 用於繪圖的Canvas
private Canvas mCanvas;
// 子執行緒標誌位
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 defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
//初始化一個SurfaceHolder物件
mHolder = getHolder();
//註冊SurfaceHolder的回撥方法
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
//mHolder.setFormat(PixelFormat.OPAQUE);
}
//SurfaceView的建立
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mIsDrawing = true;
//開啟子執行緒
new Thread(this).start();
}
//SurfaceView的改變
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
}
//SurfaceView的銷燬
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mIsDrawing = false;
}
@Override
public void run() {
//通過迴圈來不停地進行繪製
while(mIsDrawing){
draw();
}
}
private void draw() {
try {
/**
* 獲取當前的Canvas繪圖物件。
* 獲取到的Canvas物件還是繼續上次的Canvas物件,而不是一個新的物件。因此,之前的繪圖操作都將保留,
* 如果需要擦除,則可以在繪製前,通過drawColor()方法來進行清屏操作。
*/
mCanvas = mHolder.lockCanvas();
//draw something
} catch (Exception e){
} finally {
if(mCanvas != null)
//將該方法放到finally程式碼塊中,來保證每次都能將畫布內容進行提交
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
SurfaceView例項
正弦曲線
在介面上不斷繪製一個正弦曲線,類似示波器、心電圖、股票走勢圖等。只需要不斷修改橫縱座標的值,並讓它們滿足正弦函式即可。因此,使用一個Path物件來儲存正弦函式上的座標點,在子執行緒的while迴圈中,不斷改變橫縱座標值,程式碼如下:
@Override
public void run() {
while(mIsDrawing){
draw();
x += 1;
y = (int) (100 * Math.sin(x * 2 * 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);
}
}
繪製效果如下圖:
繪圖板
使用SurfaceView來實現一個簡單的繪圖板,繪圖的方法與在View中進行繪圖所使用的方法一樣,也是通過Path物件來記錄手指滑動的路徑來進行繪圖。程式碼如下:
@Override
public void run() {
/**
* 在前面的模板程式碼中,線上程中不斷地呼叫draw()方法來進行繪製,擔有的時候繪製也不用這麼頻繁。
* 因此可以在子執行緒中進行sleep操作,儘可能地節省系統資源。
*/
long start = System.currentTimeMillis();
while(mIsDrawing){
draw();
}
long end = System.currentTimeMillis();
/**
* 通過判斷draw()方法所使用的邏輯時長來確定sleep的時長,這是一個非常通用的解決方案,程式碼中的100ms是
* 一個大致的經驗值,這個值的取值一般在50ms到100ms之間。
*/
if(end - start < 100){
try {
Thread.sleep(100 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e){
} finally {
if(mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
//記錄手指滑動的路徑
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
繪製效果如下圖: