Android中提供了View進行繪圖處理,View可以滿足大部分的繪圖需求,但是有時候,View卻顯得力不從心,所以Android提供了
SurfaceView
給Android開發者,以滿足更多的繪圖需求。下面就讓我們一起來了解一下SurfaceView
。
一、爲什麼要使用SurfaceView
我們知道View是通過刷新來重繪視圖,系統通過發出
VSSYNC
信號來進行屏幕的重繪,刷新的時間間隔是16ms
,如果我們可以在16ms以內將繪製工作完成,則沒有任何問題,如果我們繪製過程邏輯很複雜,並且我們的界面更新還非常頻繁,這時候就會造成界面的卡頓,影響用戶體驗,爲此Android提供了SurfaceView
來解決這一問題。
View
和SurfaceView
的區別:
1 . View適用於主動更新的情況,而SurfaceView則適用於被動更新的情況,比如頻繁刷新界面。
2 . View在主線程中對頁面進行刷新,而SurfaceView則開啓一個子線程來對頁面進行刷新。
3 . View在繪圖時沒有實現雙緩衝機制,SurfaceView在底層機制中就實現了雙緩衝機制。
這摘錄了一段網上對於雙緩衝技術的介紹
雙緩衝技術是遊戲開發中的一個重要的技術。當一個動畫爭先顯示時,程序又在改變它,前面還沒有顯示完,程序又請求重新繪製,這樣屏幕就會不停地閃爍。而雙緩衝技術是把要處理的圖片在內存中處理好之後,再將其顯示在屏幕上。雙緩衝主要是爲了解決 反覆局部刷屏帶來的閃爍。把要畫的東西先畫到一個內存區域裏,然後整體的一次性畫出來。
二、如何使用SurfaceView
要想使用
SurfaceView
需要經過創建、初始化、使用三個步驟,下面我們就一步步來說說這三個步驟。
1 . 創建SurfaceView
我們需要自定義一個類繼承自SurfaceView,並且實現兩個接口以及接口定義的方法,當然,與自定義View類似,還要重寫三個構造函數。下面是代碼:
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
public SurfaceViewTemplate(Context context) {
this(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewTemplate(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() {
//子線程
}
}
前面三個構造函數的寫法和自定義View是相同的,接下來的三個方法分別在SurfaceView創建、改變、銷燬的時候進行調用,最後的
run()
方法中寫我們子線程中執行的繪圖邏輯即可。
2 . 初始化SurfaceView
這一步我們主要是定義三個成員變量以備後面繪圖時使用,然後初始化這三個成員變量並且註冊對應的回調方法。代碼如下:
private SurfaceHolder mSurfaceHolder;
//繪圖的Canvas
private Canvas mCanvas;
//子線程標誌位
private boolean mIsDrawing;
/**
* 初始化View
*/
private void initView(){
mSurfaceHolder = getHolder();
//註冊回調方法
mSurfaceHolder.addCallback(this);
//設置一些參數方便後面繪圖
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
}
public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//在三個參數的構造方法中完成初始化操作
initView();
}
上面的代碼很簡單,都有註釋,相信很容易看懂,這裏不再過多解釋。
3 . 使用SurfaceView
經過上面兩步的準備工作,下面就可以開始使用SurfaceView了。
這一步又可以分爲3步來完成:
(1) 通過lockCanvas()
方法獲得Canvas對象
(2) 在子線程中使用Canvas對象進行繪製
(3) 使用unlockCanvasAndPost()
方法將畫布內容進行提交
注意: lockCanvas()
方法獲得的Canvas對象仍然是上次繪製的對象,由於我們是不斷進行繪製,但是每次得到的Canvas對象都是第一次創建的Canvas對象。
SurfaceView的繪製可以使用下面的模板代碼來實現,唯一的不同就是繪製邏輯的不同,代碼如下:
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mSurfaceHolder;
//繪圖的Canvas
private Canvas mCanvas;
//子線程標誌位
private boolean mIsDrawing;
public SurfaceViewTemplate(Context context) {
this(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@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){
drawSomething();
}
}
//繪圖邏輯
private void drawSomething() {
try {
//獲得canvas對象
mCanvas = mSurfaceHolder.lockCanvas();
//繪製背景
mCanvas.drawColor(Color.WHITE);
//繪圖
}catch (Exception e){
}finally {
if (mCanvas != null){
//釋放canvas對象並提交畫布
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
/**
* 初始化View
*/
private void initView(){
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
}
}
我們在xml文件中的使用和自定義View是相同的,使用全路徑名稱即可:
<com.codekong.drawlearning.view.SurfaceViewTemplate
android:layout_width="match_parent"
android:layout_height="match_parent" />
三、SurfaceView小案例
下面我們通過兩個小案例來展示SurfaceView的使用。先放上效果圖
正弦曲線
手寫板
1 . 繪製正弦曲線
大體的框架都是上面給的那個代碼模板,區別只在於初始化畫筆,和具體的繪圖邏輯,所以這裏不再贅述,直接上代碼:
public class SurfaceViewSinFun extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mSurfaceHolder;
//繪圖的Canvas
private Canvas mCanvas;
//子線程標誌位
private boolean mIsDrawing;
private int x = 0, y = 0;
private Paint mPaint;
private Path mPath;
public SurfaceViewSinFun(Context context) {
this(context, null);
}
public SurfaceViewSinFun(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPath = new Path();
//路徑起始點(0, 100)
mPath.moveTo(0, 100);
initView();
}
@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){
drawSomething();
x += 1;
y = (int)(100 * Math.sin(2 * x * Math.PI / 180) + 400);
//加入新的座標點
mPath.lineTo(x, y);
}
}
private void drawSomething() {
try {
//獲得canvas對象
mCanvas = mSurfaceHolder.lockCanvas();
//繪製背景
mCanvas.drawColor(Color.WHITE);
//繪製路徑
mCanvas.drawPath(mPath, mPaint);
}catch (Exception e){
}finally {
if (mCanvas != null){
//釋放canvas對象並提交畫布
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
/**
* 初始化View
*/
private void initView(){
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
}
}
2 . 手寫板(隨手指繪製軌跡)
這個主要是涉及到觸摸事件,在手指按下時將Path的起始點移動到按下的座標點,手指移動時將移動的座標點加入Path中,其他的代碼是相同的。代碼如下:
public class SurfaceViewHandWriting extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mSurfaceHolder;
//繪圖的Canvas
private Canvas mCanvas;
//子線程標誌位
private boolean mIsDrawing;
//畫筆
private Paint mPaint;
//路徑
private Path mPath;
private static final String TAG = "pyh";
public SurfaceViewHandWriting(Context context) {
this(context, null);
}
public SurfaceViewHandWriting(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewHandWriting(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mPath = new Path();
mPath.moveTo(0, 100);
initView();
}
@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) {
long start = System.currentTimeMillis();
drawSomething();
long end = System.currentTimeMillis();
if (end - start < 100) {
try {
Thread.sleep(100 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@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;
}
/**
* 初始化View
*/
private void initView(){
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
setFocusable(true);
setKeepScreenOn(true);
setFocusableInTouchMode(true);
}
private void drawSomething() {
try {
//獲得canvas對象
mCanvas = mSurfaceHolder.lockCanvas();
//繪製背景
mCanvas.drawColor(Color.WHITE);
//繪製路徑
mCanvas.drawPath(mPath, mPaint);
}catch (Exception e){
}finally {
if (mCanvas != null){
//釋放canvas對象並提交畫布
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
上面還有一個細節,在繪製的時候,我們並沒有讓線程一直運行,而是讓它休眠一會,從而節約系統資源,一般建議判斷的閾值爲50-100之間即可保證用戶體驗同時節約系統資源。
轉載自:https://www.jianshu.com/p/b037249e6d31