安卓權威編程指南-筆記 (第29章定制視圖與觸摸事件)
1.定制視圖
Android自帶眾多優秀的標準視圖與組件,但有時為追求獨特的應用視覺效果,我們仍需創建定制視圖。
定制視圖分為兩大類別:
- 簡單視圖: 簡單視圖內部也可以很復雜,之所以歸為簡單類別,是因為簡單視圖不包括子視圖,而且簡單視圖幾乎總是會執行定制繪制。
- 聚合視圖:聚合視圖由其他視圖對象組成,聚合視圖通常管理著子視圖,但不負責執行定制繪制,圖形繪制任務都委托給了各個子視圖。
創建定制視圖的所需的三大步驟:
- 選擇超類。對於簡單定制視圖而言,View是個空白畫布,因此它作為超類最常見,對於聚合定制視圖,我們應選擇合適的超類布局,比如FrameLayout.
- 繼承選定的超類,並至少覆蓋一個超類構造方法。
- 覆蓋其他關鍵方法,以定制視圖行為。
1.1 創建一個簡單的視圖類
public class BoxDrawingView extends View {
// Used when creating the view in code
public BoxDrawingView(Context context) {
this(context, null);
}
// Used when inflating the view from XML
public BoxDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
從布局文件中實例化的視圖可收到一個AttributeSet實例,該實例包含了XML布局文件中指定的XML屬性。即使不打算使用構造方法,按習慣做法也應添加他們。
有了定制視圖類,可以在布局文件裏面引用它。
<com.bignerdranch.android.draganddraw.BoxDrawingView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height ="match_parent"
/>
在引用時必須使用自定義View的全路徑名,這樣布局inflater才能夠找到它,布局文件inflater解析布局XML文件,並按視圖定義創建View實例,如果元素名不是全路徑名,布局inflater
會轉而在android.view和android.widget包中尋找目標,如果目標視圖放置在其他包中,布局inflater將無法找到目標並最終導致應用崩潰。
因此,對於android.view和android.widger包以外的定制視圖類,必須指定它們的全路徑名。
1.2 處理觸摸事件
監聽觸摸事件的一種方式是使用以下view方法,設置一個觸摸事件監聽器:
public void setOnTouchListener(View.OnTouchListener l)
不過我們的定制視圖是View的子類,因此可走捷徑直接覆蓋以下View方法:
public boolean onTouchEvent(MotionEvent event)
該方法接收一個MotionEvent類實例,MotionEvent類可用來描述包括位置和動作的觸摸事件。動作用於描述事件所處的階段。
動作常量 | 動作描述 |
ACTION_DOWN | 手指觸摸到屏幕 |
ACTION_MOVE | 手指在屏幕上移動 |
ACTION_UP | 手指離開屏幕 |
ACTION_CANCEL | 父視圖攔截了觸摸事件 |
在onTouchEvent()實現方法中,可使用以下MotionEvent方法查看動作值:
public final int getAction()
我們的目的就是在一根手指放下的時候記錄下放下的位置,移動時隨之變化,放開時固定該矩形框。並且之前畫的矩形框數據需要記錄下來。
建立一個實體類,用於表示一個矩形框的定義數據。用來保存原始坐標點(手指的初始位置)和當前坐標點(手指的當前位置):
public class Box {
private PointF mOrigin;
private PointF mCurrent;
public Box(PointF origin) {
mOrigin = origin;
mCurrent = origin;
}
//get、set略
}
然後重寫onTouchEvent()方法並進行相應操作:
private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();
@Override
public boolean onTouchEvent(MotionEvent event) {
// 每次有觸摸事件都記錄下現在的坐標
PointF current = new PointF(event.getX(), event.getY());
String action = "";
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
action = "ACTION_DOWN";
// 每次按下的時候在列表中中新增一個 Box
mCurrentBox = new Box(current);
mBoxen.add(mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
action = "ACTION_MOVE";
if (mCurrentBox != null) {
// 移動的時候都要重繪
mCurrentBox.setCurrent(current);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
// 擡起的時候不再指向最新的 Box
action = "ACTION_UP";
mCurrentBox = null;
break;
case MotionEvent.ACTION_CANCEL:
action = "ACTION_CANCEL";
mCurrentBox = null;
break;
}
Log.i(TAG, action + " at x=" + current.x +
", y=" + current.y);z
return true;
}
在取消觸摸事件或用戶手指離開屏幕時,應清空mCurrentBox以結束屏幕繪制,以完成的Box會安全的存儲在數組中。
invalidate()方法會強制BoxDrawingView重新繪制自己,這樣在拖拽時就能實時看到矩形框。
2 onDraw()方法內的圖形繪制
應用啟動時,所有視圖都處於無效狀態(視圖還沒有繪制到屏幕上),為解決這個問題,Android調用了頂級View視圖的draw()方法,這會引起自上而下的鏈式調用反映。
首先,視圖完成自我繪制,然後是子視圖的自我繪制,再然後是子視圖的子視圖的自我繪制,如此調用下去直至繼承結構的末端。當繼承結構中的所有視圖都完成自我繪制後,最頂級
View 視圖也就生效了。
為加入這種繪制,可覆蓋以下 View 方法:
protected void onDraw(Canvas canvas);
在onTouchEvent()方法中響應ACTION_MOVE動作時,我們調用invalidate()方法再次讓BoxDrawingView處於失效狀態,這迫使他重新完成自我繪制,並再次調用onDraw()方法。
Canvas和Paint是Android系統的兩大繪制類。
- Canvas類擁有我們需要的所有繪制操作,其方法可決定在哪裏以及繪什麽,比如線條、圓形、字詞、矩形等。
- Paint類決定如何繪制,其方法可指定繪制圖形的特征,例如是否填充圖形、使用什麽字體繪制、線條是什麽顏色等
public BoxDrawingView(Context context, AttributeSet attrs){ //AttributeSet實例包含了XML布局文件中指定的XML屬性。 super(context,attrs); mBoxPaint = new Paint(); mBoxPaint.setColor(0x22ff0000); mBackgroundPaint = new Paint(); mBackgroundPaint.setColor(0xfff8efe0); } @Override protected void onDraw(Canvas canvas){ //先畫出背景 canvas.drawPaint(mBackgroundPaint); //畫出每個繪制過的矩形 for(Box box : mBoxen){ float left = Math.min(box.getOrigin().x, box.getCurrent().x); float right = Math.max(box.getOrigin().x, box.getCurrent().x); float top = Math.min(box.getOrigin().y, box.getCurrent().y); float bottom = Math.max(box.getOrigin().y,box.getCurrent().y); canvas.drawRect(left, top ,right ,bottom, mBoxPaint); } }
以上代碼的第一部分簡單直接:使用米白背景paint,填充canvas以襯托矩形框。然後,針對矩形框數組中的每一個矩形框,據其兩點坐標,確定矩形框上下左右的位置。繪制時,左端和頂端的值作為最小值,右端和底端的值作為最大值。完成位置坐標值計算後,調用 Canvas.drawRect(...) 方法,在屏幕上繪制紅色矩形框。
安卓權威編程指南-筆記 (第29章定制視圖與觸摸事件)