個人讀書筆記,部分沒讀懂的知識點(diǎn)可能會簡單概括或缺失,以后反復(fù)閱讀后再完善。
第3章 Android控件架構(gòu)與自定義控件詳解
3.1 Android 控件結(jié)構(gòu)
Android控件大致分為兩類:ViewGroup控件與View控件。
通過ViewGroup控件,界面上的控件形成一個樹形結(jié)構(gòu)(控件樹)。
上層控件負(fù)責(zé)下層控件的測量與繪制,并傳遞交互事件。
Activity的findViewById()方法,就是在控件樹中以樹的深度優(yōu)先遍歷查找對應(yīng)元素。
ViewParent 是整個控件樹的核心。

Activity使用setContentView()方法來把整個DecorView添加到PhoneWindow中。

3.2 View的測量
Android通過MeasureSpec類測量View,有三種測量模式:
·EXACTLY
精確模式,比如android:layout_width="100dp",使用的是EXACTLY模式(默認(rèn))。
·AT_MOST
最大值模式,比如wrap_content,只要不超過父控件允許的最大尺寸,控件大小隨內(nèi)容變化而變化。
·UNSPECIFIED
繪制自定義View時使用
View類默認(rèn)的onMeasure()只支持EXACTLY模式,所以自定義控件的時候最好重寫onMeasure()方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec)
);
}
private int measureHeight(int heightMeasureSpec) {
int result=0;
int specMode=MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result=specSize;
}else {
result=200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result=0;
int specMode=MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result=specSize;
}else {
result=200;
if (specMode == MeasureSpec.AT_MOST) {
//取最?。ú荒艹^父控件)
result = Math.min(result, specSize);
}
}
return result;
}
3.3 View的繪制
簡單介紹了Canvas對象。使用Canvas通常需要通過繼承View并重寫onDraw()方法。
在onDraw()方法里有一個canvas參數(shù),使用它就能繪圖。
在其他地方需要創(chuàng)建一個Canvas對象:
Canvas canvas = new Canvas(bitmip);
創(chuàng)建時傳一個bitmap對象時為了儲存使用繪制在Canvas上的像素信息。
3.4 ViewGroup的測量
ViewGroup通過遍歷子View的大小,從而決定自己的大小。
自定義ViewGroup,通常會取重寫onLayout()方法控制子View顯示位置的邏輯。
如果需要支持wrap_content屬性,它也像View一樣得去重寫onMeasure()方法。
3.5 ViewGroup的繪制
ViewGroup的onDraw()方法只會在指定背景顏色時調(diào)用。
ViewGroup使用dispatchDraw()方法來繪制子View,過程:遍歷->調(diào)用子View的繪制方法。
3.6 自定義View
View中比較重要的回調(diào)方法:
·onFinishInflate(): 從XML加載組件后回調(diào)。
·onSizeChanged(): 組件大小改變時回調(diào)。
·onMeasure(): 測量方法。
·onLayout(): 確定顯示的位置。
·onTouchEvent(): 監(jiān)聽觸摸事件。
有三種方法實(shí)現(xiàn)自定義的控件:
·對現(xiàn)有控件進(jìn)行拓展
書中演示了一個textView例子。
·創(chuàng)建復(fù)合控件
演示了一個TopBar控件。
·重寫View來實(shí)現(xiàn)全新的控件
演示了弧線展示圖和音頻條形圖。
3.7 自定義ViewGroup
自定義ViewGroup需要重寫onMeasure()方法來對子View進(jìn)行測量,重寫onLayout()方法來確定子View的位置,重寫onTouchEvent()方法增加響應(yīng)事件。
演示了一個類似ScrollView的自定義ViewGroup
3.8 事件攔截機(jī)制分析
如果一個觸摸事件,子View和父ViewGroup都有可能想處理,這就需要事件攔截。
在ViewGroup中,事件有三個方法:
·dispatchTouchEvent()
·onInterceptTouchEvent()
·onTouchEvent()
而View只有兩個方法:
·dispatchTouchEvent()
·onTouchEvent()
View比ViewGroup少了個onInterceptTouchEvent()方法,這個onInterceptTouchEvent()方法就是事件攔截的核心代碼。
事件傳遞時,先執(zhí)行dispatchTouchEvent()方法,再執(zhí)行onInterceptTouchEvent()方法。
事件處理時,都是執(zhí)行onTouchEvent()方法。
假設(shè)有三層結(jié)構(gòu),
最外層叫它MyViewGroupA,
第二層叫它MyViewGroupB,
最里層叫它MyView。
事件傳遞時,dispatchTouchEvent()方法一般不會去改寫,所以暫時不管。
則事件的傳遞過程:
MyViewGroupA -> onInterceptTouchEvent() A -> MyViewGroupB -> onInterceptTouchEvent() B -> MyView
事件的處理過程:
onTouchEvent() View -> onTouchEvent() B -> onTouchEvent() A
方法的返回值都是布爾值,初始情況下返回值都是false(都往下傳遞\往上處理)。
如果在MyViewGroupA的onInterceptTouchEvent()中返回true,則MyViewGroupA的三個方法都跑一遍,事件結(jié)束。
如果在MyViewGroup的onInterceptTouchEvent()B中返回true,則在MyViewGroupA的onTouchEvent()響應(yīng)前,會把MyViewGroupB的三個方法都跑一遍。