對於自定義View,其繪製的基本步驟爲measure(測量),layout(佈局),draw(繪製)三個階段。而ViewRootImpl作爲連接view和android系統的重要組成部分(ViewRootImpl 類,是連接 WindowManager 和 DecorView 的紐帶),在這裏不得不提。我們可以把ViewRootImpl的根,View是在ViewRootImpl的基礎上建立起來的。下面以一幅圖作爲理解
ViewRootImpl層 preformMeasure()、preformLayout()、preformDraw()
ViewRootImpl層 mView.measure()、mView.layout()、draw()(直接在ViewRootImpl中被引用)
用戶層Onmeasure()、OnLayout()、OnDraw()方法。
其中preformMeasure調用了measure(),measure()調用了Onmeasure()其他兩者也是這種情況。
PS:layout()除了調用onLayout()外還調用了了onMeasure().
measure()方法有兩種典型的調用方式。
在ViewRootImpl類中被調用:
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
在ViewGroup中被調用:
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
從上面這兩種調用方式,我們可以知道measure()主要用來測量子容器的大小,其中的參數由父容器來決定。(ViewGroup是child的父容器,ViewRootImpl可以看成是View的父容器)。
對於ViewRootImp中調用measure方法的情況,我們可以通過其源碼來分析:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//doTraversal()方法中調用了performtraversal()
void doTraversal() {
...
performtraversal();
...
}
對performtraversal()方法的分析至關重要。
private void performTraversals() {
//1372行 Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
...
//1428行
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
//1767行
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//1793行
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//1829行
performLayout();
//1968行
performDraw();
}
其中measureHierarchy方法中多次調用了performMeasure方法,而且在performLayout中被調用。
也就是performLayout調用measureHierarchy,measureHierarchy調用了performMeasure.
從上面可以看出,繪製View時,遵循了先測量後佈局再繪製的原則。
下面的部分爲自己下篇文章所要深入探討的問題,也就是DecorView
對於 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的LayoutParams 來共同確定
對於應用層 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來共同決定
google 官方Demo
CustomView.java
大概思路是這樣的:PieChart繼承自ViewGroup,這個ViewGoup容器中有PieView和PointerView。畫圖步驟如下:爲了簡化畫圖流程,以固定layout_width = 200dp , layout_height = 200dp爲例來進行說明,其實WARP_CONTENT和FILL_PARENT情況差不多,只是由於需要先遍歷子View之後才能確定ViewGroup大小,稍微複雜一點。
- PieChart構造函數中初始化PieView和PointerView並addview。
- PieChart調用Onmeasure方法測量尺寸。
- 這時,PieChart的尺寸變化了,就會自動調用PieChart的onSizeChanged()方法。在onSizeChanged()方法中調用了mpieView.layout()方法,由於layout()方法用於將子View佈局到PieChart上,這時候pieView將登場
- 在PieView繪製的過程中,onMeasure()之後就是onSizeChanged(),在調試的過程中發現沒有進入onMeasure方法,不知道爲什麼。
- 待PieView在PieChart上佈局完之後(PieView本身的OnDraw方法還沒有執行),PieChart繼續未完成的工作,OnLayout,OnDraw
- PieChart執行完一次完整繪圖之後,子View(PieView)就接着完成未完成的任務onDraw()
所以總結爲先是ViewGroup測量,然後在ViewGroup的onSizeChanged方法 中佈局PieView,調用了PieView.layout(l,t,r,b)方法(也就是調用了OnLayout()),這樣PieView的尺寸就會發生變化(4個座標軸的值由0變成l,t,r,b)。子View佈局完成之後,ViewGroup開始佈局OnLayout()和繪圖OnDraw()。打印結果如下:
04-06 20:25:53.646 12384-12384/com.example.tony.recyclerview E/PieView﹕ ----PieView()-----
04-06 20:25:53.646 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------Before addView
04-06 20:25:53.647 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------after addview
04-06 20:25:53.672 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.712 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.712 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged w = 400
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged h = 400
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged oldw = 0
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged oldh = 0
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------before mPieView.layout()
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onSizeChanged----
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ onsizechanged w = 360
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ onsizechanged h = 360
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ ---------onLayout----
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------after mPieView.layout()
04-06 20:25:53.714 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onLayout------------
04-06 20:25:53.724 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:53.736 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
04-06 20:25:53.784 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.784 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onLayout------------
04-06 20:25:53.787 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:53.801 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
04-06 20:25:54.064 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:54.077 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
根據對draw(Canvas c)源碼分析發現,繪圖的過程可以分成5步,其中重要的的步驟有
- draw the backgroud if needed
- draw the content(在這裏會調用OnDraw())
- draw the children(在這裏會調用子View的draw()方法,也就會調用相應的OnDraw()方法)
- draw the decorations(foregroud,scrllbars)
所以上面的調用順序就不難理解了。上面的分析還有一個問題:子view在佈局的時候爲什麼沒有進入OnMeasure方法,不是很理解