onMeasure onLayout onDraw

對於自定義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大小,稍微複雜一點。

  1. PieChart構造函數中初始化PieView和PointerView並addview。
  2. PieChart調用Onmeasure方法測量尺寸。
  3. 這時,PieChart的尺寸變化了,就會自動調用PieChart的onSizeChanged()方法。在onSizeChanged()方法中調用了mpieView.layout()方法,由於layout()方法用於將子View佈局到PieChart上,這時候pieView將登場
  4. 在PieView繪製的過程中,onMeasure()之後就是onSizeChanged(),在調試的過程中發現沒有進入onMeasure方法,不知道爲什麼。
  5. 待PieView在PieChart上佈局完之後(PieView本身的OnDraw方法還沒有執行),PieChart繼續未完成的工作,OnLayout,OnDraw
  6. 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方法,不是很理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章