View measure源碼分析

View繪制流程

一圖勝千言


DecorView添加至窗口的流程

這里說明下Acitivity的onResume是在handleResumeActivity之前執(zhí)行的蛤织,所以在onResume中獲取View的寬高為0。
實(shí)際上通用的繪制流程應(yīng)該是從WindowManager#addView開始鳄炉。

View measure()分析

首先View的Measure方法聲明為final边苹,子類無法繼承,故關(guān)于View多態(tài)的實(shí)現(xiàn)就只能在onMeasure方法中實(shí)現(xiàn)

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
   //1.判斷View本身LayoutMode是否是視覺邊界布局沃于,僅用于檢測ViewGroup
   boolean optical = isLayoutModeOptical(this);
   //2.判斷父容器是否是視覺布局邊界它呀,是則重新調(diào)整測量規(guī)格(mParent可能是ViewRootImpl)
        if (optical != isLayoutModeOptical(mParent)) {
            //View的background需要設(shè)置.9背景圖才會(huì)生效,否則insets的left洛勉、right全為0
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
   // Suppress sign extension for the low bytes,禁止低位進(jìn)行符號(hào)擴(kuò)展
  //3.根據(jù)當(dāng)前測量規(guī)格生成一個(gè)與之對應(yīng)的key(相同的測量規(guī)格產(chǎn)生的測量值肯定一樣的)粘秆,供后續(xù)索引緩存測量值
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
  //4.初始化測量緩存稀疏數(shù)組,該數(shù)組可自行擴(kuò)容收毫,只是初始化為2
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); 
  //5.如果強(qiáng)制layout(eg.view.forceLayout())或者本次測量規(guī)格與上次測量規(guī)格不同攻走,進(jìn)入該if語句
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
 final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
           // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();
  //6.如果需要強(qiáng)制layout則進(jìn)行重新測量,反之則從緩存中查詢是否有與目前測量規(guī)格對應(yīng)的key此再,
    //如果有則取用緩存中的測量值昔搂,反之則執(zhí)行onMeasure方法重新測量,未索引到返回一個(gè)負(fù)數(shù)
  int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  //7.targetSDK小于Kitkat版本(API20),sIgnoreMeasureCache則為true
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
         }  else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
            //8.標(biāo)志位賦值引润,表明需要在layout方法中執(zhí)行onlayout方法
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
       //9.緩存本次測量規(guī)格,及測量寬高
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
       (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}


  /**
     * Return true if o is a ViewGroup that is laying out using optical bounds.
     * @hide
     */
    public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
    }

關(guān)于LayoutMode請參考Android LayoutMode需要翻墻
關(guān)于MeasureSpec請參考Android MeasureSpec
小結(jié):
View的measure方法只是一個(gè)測量優(yōu)化者痒玩,主要做了2級(jí)測量優(yōu)化:
1.如果flag不為強(qiáng)制Layout或者與上次測量規(guī)格相比未改變淳附,那么將不會(huì)進(jìn)行重新測量(執(zhí)行onMeasure方法),直接使用上次的測量值蠢古;
2.如果滿足非強(qiáng)制測量條件奴曙,即前后測量規(guī)格發(fā)生變化,則會(huì)先根據(jù)目前測量規(guī)格生成的key索引緩存數(shù)據(jù)草讶,索引到就無需進(jìn)行重新測量;如果targetSDK小于API 20則二級(jí)測量優(yōu)化無效洽糟,依舊會(huì)重新測量,不會(huì)采用緩存測量值。
3.View#requestLayout 只會(huì)讓該View及其父容器重新走一遍坤溃,如果該View是ViewGroup拍霜,其里面的子View測量優(yōu)化還是依舊有效的

View onMeasure()分析

View的onMeasure方法比較簡單,目的是將測量值賦給mMeasuredWidth和mMeasuredHeight

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //未指定則使用最小值
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //解析測量規(guī)格獲得寬薪介、高祠饺,這就是為何View無論你填match_parent還是wrap_content,它始終是填滿父容器的原因
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        //表明測量尺寸已經(jīng)設(shè)置汁政,與measure方法中的first clears the measured dimension flag相呼應(yīng)
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

小結(jié):
View的onMeasure方法才是真正的測量者道偷,它根據(jù)測量規(guī)格以及其他條件來決定自己最終的測量大小。
需要注意记劈,自定義View重寫該方法時(shí)勺鸦,務(wù)必保證調(diào)用setMeasuredDimension()將測量寬、高存起來目木,measure方法分析中有提到换途,如果不調(diào)用該方法將會(huì)拋出非法狀態(tài)異常。

ViewGroup onMeasure分析

ViewGroup繼承至View實(shí)現(xiàn)了ViewParent接口嘶窄,是一個(gè)抽象類怀跛。前面也提到View的measure方法不能被繼承,所以ViewGroup沒有measure方法柄冲。查看源碼發(fā)現(xiàn)它并沒有重寫onMeasure方法吻谋,那就去看看其實(shí)現(xiàn)類是否有重寫,就看最簡單的實(shí)現(xiàn)類FrameLayout现横。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        //判斷是否要再次測量layout_width/height屬性為match_parent的child
        //即FrameLayout的layout_width/height屬性為wrap_content漓拾,則會(huì)再次測量屬性為match_parent的child
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();
        //首次遍歷測量子控件
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //GONE類型child不測量,這就是為何GONE不會(huì)占用位置戒祠,因?yàn)闆]有測量
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ..................
                }
            }
        ..................
        //保存自身的測量寬高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        //再次測量layout_width/height屬性為match_parent的child,部分View三次執(zhí)行onMeasure的原因
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                  ..........
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
}

進(jìn)入MeasureChildWithMargins函數(shù)

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //根據(jù)ViewGroup自身的測量規(guī)格生成child的測量規(guī)格
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        //調(diào)用child的measure方法骇两,并將child的測量規(guī)格傳遞給child
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

進(jìn)入getChildMeasureSpec方法,看看到底是如何測量child的規(guī)格的

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //獲取父容器的測量模式姜盈、測量大小
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //計(jì)算出父容器允許child的最大尺寸
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 父容器測量模式為精確模式
        case MeasureSpec.EXACTLY:
        //如果child的layout_width/height為具體的數(shù)值eg.20dp低千,那么child的測量規(guī)格就為大小20dp,模式為精確模式
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            }
 //如果child的layout_width/height為MATCH_PARENT馏颂,那么child的測量規(guī)格就為大小父容器允許的最大值示血,模式為精確模式
            else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } 
 //如果child的layout_width/height為WRAP_CONTENT,那么child的測量規(guī)格就為大小父容器允許的最大值救拉,模式為AT_MOST
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

小結(jié):
ViewGroup的測量主要是根據(jù)其自身測量規(guī)格难审,結(jié)合child的LayoutParams進(jìn)行判斷分析,生成一個(gè)child的測量規(guī)格信息亿絮,傳遞給child的measure方法告喊。所謂的測量規(guī)格即是一個(gè)建議麸拄,建議view的寬、高應(yīng)該為多少黔姜,至于采取與否完全取決于view自己(嗯應(yīng)該是取決于程序員O(∩_∩)OBG小)。
另ViewGroup提供了三個(gè)測量方法供我們使用地淀,在實(shí)際運(yùn)用中可以偷偷懶失球,不用自己去實(shí)現(xiàn)測量邏輯:

  1. measureChildWithMargins 測量單個(gè)child,margin參數(shù)有效
  2. measureChild 測量單個(gè)child帮毁,margin參數(shù)無效
  3. measureChildren 測量所有child內(nèi)部調(diào)用measureChild

延伸閱讀:
【View為什么會(huì)至少執(zhí)行2次onMeasure实苞、onLayout】暫未發(fā)布

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市烈疚,隨后出現(xiàn)的幾起案子黔牵,更是在濱河造成了極大的恐慌,老刑警劉巖爷肝,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾浦,死亡現(xiàn)場離奇詭異,居然都是意外死亡灯抛,警方通過查閱死者的電腦和手機(jī)金赦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來对嚼,“玉大人夹抗,你說我怎么就攤上這事∽菔” “怎么了漠烧?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長靡砌。 經(jīng)常有香客問我已脓,道長,這世上最難降的妖魔是什么通殃? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任度液,我火速辦了婚禮,結(jié)果婚禮上画舌,老公的妹妹穿的比我還像新娘堕担。我一直安慰自己,他們只是感情好骗炉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布照宝。 她就那樣靜靜地躺著蛇受,像睡著了一般句葵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天乍丈,我揣著相機(jī)與錄音剂碴,去河邊找鬼。 笑死轻专,一個(gè)胖子當(dāng)著我的面吹牛忆矛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播请垛,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼催训,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宗收?” 一聲冷哼從身側(cè)響起漫拭,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎混稽,沒想到半個(gè)月后采驻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匈勋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年礼旅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽洁。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痘系,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诡挂,到底是詐尸還是另有隱情碎浇,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布璃俗,位于F島的核電站奴璃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏城豁。R本人自食惡果不足惜苟穆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唱星。 院中可真熱鬧雳旅,春花似錦、人聲如沸间聊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哎榴。三九已至型豁,卻和暖如春僵蛛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迎变。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工充尉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衣形。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓驼侠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谆吴。 傳聞我的和親對象是個(gè)殘疾皇子倒源,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容