1、View繪制流程
- onMeasure() 確定View的測量寬高。
- onLayout() 確定View的最終寬高和四個頂點的位置。
- onDraw() 將View?繪制到屏幕上。
2、MeasureSpec有三種測量模式:
2.1.?EXACTLY
(精確模式)
- 含義:父容器明確指定了子
View
的精確尺寸,子View
必須使用該尺寸。 - 典型場景:
- 布局中設置了固定值(如
android:layout_width="100dp"
)。 - 子
View
的寬/高設置為match_parent
,且父容器有確定尺寸。
- 布局中設置了固定值(如
- 子
View
行為:必須直接使用MeasureSpec
中的size
作為最終尺寸。
2.2.?AT_MOST
(最大模式)
-
含義:父容器指定了子
View
的最大可用尺寸,子View
的尺寸不能超過該值,但可以更小。 -
典型場景:
-
子
View
的寬/高設置為wrap_content
。 -
父容器為
ScrollView
或RecyclerView
等可滾動的容器。
-
-
子
View
行為:根據自身內容計算尺寸,但最終尺寸不能超過MeasureSpec
中的size
。
2.3.?UNSPECIFIED
(未指定模式)
- 含義:父容器對子
View
無任何約束,子View
可以自由決定尺寸(通常根據自身邏輯或內容)。 - 典型場景:
- 自定義
View
或ViewGroup
需要多次測量(如ListView
測量子View
的高度)。 - 系統內部測量(如
ScrollView
在測量子View
的滾動范圍時)。
- 自定義
- 子
View
行為:完全由自身決定尺寸(可能使用默認值或內容所需尺寸)。
3、事件分發機制
3.1 事件分發:dispatchTouchEvent
用來進行事件的分發,如果事件能夠傳遞給當前View,則該方法一定會被調用。返回結果受當前View的onTouchEvent和下級的dispatchTouchEvent的影響,表示是否消耗當前事件。
原型:public boolean dispatchTouchEvent(MotionEvent ev)
return:
- ture:當前View消耗所有事件
- false:停止分發,交由上層控件的onTouchEvent方法進行消費,如果本層控件是Activity,則事件將被系統消費,處理
3.2?事件攔截:onInterceptTouchEvent
需注意的是在Activity,ViewGroup,View中只有ViewGroup有這個方法。故一旦有點擊事件傳遞給View,則View的onTouchEvent方法就會被調用
在dispatchTouchEvent內部使用,用來判斷是否攔截事件。如果當前View攔截了某個事件,那么該事件序列的其它方法也由當前View處理,故該方法不會被再次調用,因為已經無須詢問它是否要攔截該事件。
原型:public boolean onInterceptTouchEvent(MotionEvent ev)
return:
- ture:對事件攔截,交給本層的onTouchEvent進行處理
- false:不攔截,分發到子View,由子View的dispatchTouchEvent進行處理
- super.onInterceptTouchEvent(ev):默認不攔截
3.3 事件處理:onTouchEvent
在dispatchTouchEvent中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一事件序列中,當前View無法再接受到剩下的事件,并且事件將重新交給它的父元素處理,即父元素的onTouchEvent會被調用
原型:public boolean onTouchEvent(MotionEvent ev)
return:
- true:表示onTouchEvent處理后消耗了當前事件
- false:不響應事件,不斷的傳遞給上層的onTouchEvent方法處理,直到某個View的onTouchEvent返回true,則認為該事件被消費,如果到最頂層View還是返回false,則該事件不消費,將交由Activity的onTouchEvent處理。
- super.onTouchEvent(ev):默認消耗當前事件,與返回true一致。
事件分發機制總結:
在分析事件分發機制時,應該從事件分發的順序入手一步一步解剖。從上文我們知道事件分發順序為:Activity->Window->DecorView->ViewGroup->View。由于Window與DecorView可以看作是Activity->ViewGroup的過程,故這里將從三部分通過源碼來分析事件分發機制:
- Activity對點擊事件的分發機制
- ViewGroup對點擊事件的分發機制
- View對點擊事件的分發機制
- 當一個點擊事件發生后,總是先傳遞給當前的Activity,由Activity的dispatchTouchEvent進行分發,而Activity會將事件傳遞給Window,然后由Window的唯一實現類PhoneWindow將事件傳遞給DecorView,接著DecorView將事件傳遞給自己的父類ViewGroup,此時的ViewGroup就是通過setContentView所設置的View,故可以稱為頂級View,這時候ViewGroup可能是自己處理該事件或者傳遞給子View,但是最終都會調用View的dispatchTouchEvent來處理事件。
- 在View的dispatchTouchEvent中,如果設置了onTouchListener,會調用其onTouch方法,如果onTouch返回true,則不再調用onTouchEvent。如果有設置點擊事件,則在onTouchEvent會調用onClick方法。如果子View的onTouchEvent返回了false,則表示不消耗事件,事件會回傳給上一級的ViewGroup的onTouchEvent,如果所有的ViewGroup都沒有返回true,則最終會回傳到Activity的onTouchEvent。
4、requestLayout(), invalidate(), postInvalidate() 方法區別
requestLayout方法只會導致當前view的measure和layout,而draw不一定被執行,只有當view的位置發生改變才會執行draw方法,因此如果要使當前view重繪需要調用invalidate。
invalidate在UI線程中調用,postInvalidate在非UI線程中調用。因為android的UI線程是非線程安全的,所以在非UI線程中,需要使用postInvalidate來使View重繪。view調用invalidate將導致當前view的重繪(draw調用),view的父類將不會執行draw方法;viewGroup調用invalidate會使viewGroup的子view調用draw,也就是viewGroup內部的子view進行重繪;