##4.1 初始ViewRoot和DecorView
(1)`ViewRoot`對應`ViewRootImpl`類,它是連接`WindowManager`和`DecorView`的紐帶,View的三大流程均通過ViewRoot來完成。
(2)`ActivityThread`中,Activity創建完成后,會將DecorView添加到Window中,同時創建ViewRootImpl對象,并建立兩者的關聯。
(3)View的繪制流程從ViewRoot的`performTraversals`方法開始,經過`measure`、`layout`和`draw`三大流程。
(4)`performMeasure`方法中會調用`measure`方法,在`measure`方法中又會調用`onMeasure`方法,在onMeasure方法中會對所有的子元素進行measure過程,這個時候measure流程就從父容器傳遞到子元素了,這樣就完成了一次measure過程,layout和draw的過程類似。 (書中175頁畫出詳細的圖示)
(5)measure過程決定了view的寬高,在幾乎所有的情況下這個寬高都等同于view最終的寬高。layout過程決定了view的四個頂點的坐標和view實際的寬高,通過`getWidth`和`getHeight`方法可以得到最終的寬高。draw過程決定了view的顯示。
(6)DecorView其實是一個FrameLayout,其中包含了一個豎直方向的LinearLayout,上面是標題欄,下面是內容欄(id為`android.R.id.content`)。
## 4.2 理解MeasureSpec
(1)`MeasureSpec`和`LayoutParams`的對應關系
在view測量的時候,系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然后再根據這個MeasureSpec來確定View測量后的寬高。
MeasureSpec不是唯一由LayoutParams決定的,LayoutParams需要和父容器一起才能決定view的MeasureSpec,從而進一步確定view的寬高。對于DecorView,它的MeasureSpec由窗口的尺寸和其自身的LayoutParams來決定;對于普通view,它的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定。
(2)普通view的MeasureSpec的創建規則 (書中182頁列出詳細的表格)
當view采用固定寬高時,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精確模式,并且大小是LayoutParams中的大小。
當view的寬高是`match_parent`時,如果父容器的模式是精確模式,那么view也是精確模式,并且大小是父容器的剩余空間;如果父容器是最大模式,那么view也是最大模式,并且大小是不會超過父容器的剩余空間。
當view的寬高是`wrap_content`時,不管父容器的模式是精確模式還是最大模式,view的模式總是最大模式,并且大小不超過父容器的剩余空間。
## 4.3 view的工作流程
(1)view的measure過程和Activity的生命周期方法不是同步執行的,因此無法保證Activity執行了`onCreate`、`onStart`、`onResume`時某個view已經測量完畢了。如果view還沒有測量完畢,那么獲得的寬高就都是0。下面是四種解決該問題的方法:
1.`Activity/View # onWindowFocusChanged`方法
`onWindowFocusChanged`方法表示view已經初始化完畢了,寬高已經準備好了,這個時候去獲取寬高是沒問題的。這個方法會被調用多次,當Activity繼續執行或者暫停執行的時候,這個方法都會被調用。
2.`view.post(runnable)`
通過post將一個runnable投遞到消息隊列的尾部,然后等待Looper調用此runnable的時候,view也已經初始化好了。
3.`ViewTreeObserver`
使用`ViewTreeObserver`的眾多回調方法可以完成這個功能,比如使用`onGlobalLayoutListener`接口,當view樹的狀態發生改變或者view樹內部的view的可見性發生改變時,`onGlobalLayout`方法將被回調。伴隨著view樹的狀態改變,這個方法也會被多次調用。
4.`view.measure(int widthMeasureSpec, int heightMeasureSpec)`
通過手動對view進行measure來得到view的寬高,這個要根據view的LayoutParams來處理:
`match_parent`:無法measure出具體的寬高;
`wrap_content`:如下measure,設置最大值
~~~
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
~~~
精確值:例如100px
~~~
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
~~~
(2)在view的默認實現中,view的測量寬高和最終寬高是相等的,只不過測量寬高形成于measure過程,而最終寬高形成于layout過程。
(3)draw過程大概有下面幾步:
1.繪制背景:`background.draw(canvas)`;
2.繪制自己:`onDraw()`;
3.繪制children:`dispatchDraw`;
4.繪制裝飾:`onDrawScrollBars`。
## 4.4 自定義view
(1)繼承view重寫onDraw方法需要自己支持`wrap_content`,并且`padding`也要自己處理。繼承特定的View例如TextView不需要考慮。
(2)盡量不要在View中使用Handler,因為view內部本身已經提供了`post`系列的方法,完全可以替代Handler的作用。
(3)view中如果有線程或者動畫,需要在`onDetachedFromWindow`方法中及時停止。
(4)處理好view的滑動沖突情況。
接下來是原書中的自定義view的示例,推薦閱讀[源碼](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_4/src/com/ryg/chapter_4/ui/CircleView.java)。
- 前言
- 讀書筆記(1)第1章 Activity的生命周期和啟動模式
- 讀書筆記(2)第2章 IPC機制
- 讀書筆記(3)第3章 View的事件體系
- 讀書筆記(4)第4章 View的工作原理
- 讀書筆記(5)第5章 理解RemoteViews
- 讀書筆記(6)第6章 Android的Drawable
- 讀書筆記(7)第7章 Android動畫深入分析
- 讀書筆記(8)第8章 理解Window和WindowManager
- 讀書筆記(9)第9章 四大組件的工作過程
- 讀書筆記(10)第10章 Android的消息機制
- 讀書筆記(11)第11章 Android的線程和線程池
- 讀書筆記(12)第12章 Bitmap的加載和Cache
- 讀書筆記(13)第13章 綜合技術