measure流程

一漱挚、 measure

了解measure過程前,需要先了解傳遞尺寸(寬 / 高測(cè)量值)的2個(gè)類:

  • ViewGroup.LayoutParams類()
    • 指定視圖View 的高度(height) 和 寬度(width)等布局參數(shù)
  • MeasureSpecs 類(父視圖對(duì)子視圖的測(cè)量要求)
    • 測(cè)量規(guī)格(MeasureSpec) = 測(cè)量模式(mode) + 測(cè)量大小(size),
    • 通過使用二進(jìn)制,將測(cè)量模式(mode) & 測(cè)量大小(size)打包成一個(gè)int值來,并提供了打包 & 解包的方法
    • 測(cè)量模式(Mode)的類型有3種:UNSPECIFIED哮笆、EXACTLY 和
      AT_MOST
image

二、MeasureSpec的計(jì)算

/**
  * 源碼分析:getChildMeasureSpec()
  * 作用:根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams汰扭,計(jì)算單個(gè)子View的MeasureSpec
  * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
  **/

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
     //參數(shù)說明
     * @param spec 父view的詳細(xì)測(cè)量值(MeasureSpec) 
     * @param padding view當(dāng)前尺寸的的內(nèi)邊距和外邊距(padding,margin) 
     * @param childDimension 子視圖的布局參數(shù)(寬/高)

        //父view的測(cè)量模式
        int specMode = MeasureSpec.getMode(spec);     

        //父view的大小
        int specSize = MeasureSpec.getSize(spec);     
      
        //通過父view計(jì)算出的子view = 父大小-邊距(父要求的大小稠肘,但子view不一定用這個(gè)值)   
        int size = Math.max(0, specSize - padding);  
      
        //子view想要的實(shí)際大小和模式(需要計(jì)算)  
        int resultSize = 0;  
        int resultMode = 0;  
      
        //通過父view的MeasureSpec和子view的LayoutParams確定子view的大小  


        // 當(dāng)父view的模式為EXACITY時(shí),父view強(qiáng)加給子view確切的值
       //一般是父view設(shè)置為match_parent或者固定值的ViewGroup 
        switch (specMode) {  
        case MeasureSpec.EXACTLY:  
            // 當(dāng)子view的LayoutParams>0萝毛,即有確切的值  
            if (childDimension >= 0) {  
                //子view大小為子自身所賦的值项阴,模式大小為EXACTLY  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  

            // 當(dāng)子view的LayoutParams為MATCH_PARENT時(shí)(-1)  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                //子view大小為父view大小,模式為EXACTLY  
                resultSize = size;  
                resultMode = MeasureSpec.EXACTLY;  

            // 當(dāng)子view的LayoutParams為WRAP_CONTENT時(shí)(-2)      
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                //子view決定自己的大小笆包,但最大不能超過父view环揽,模式為AT_MOST  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }  
            break;  
      
        // 當(dāng)父view的模式為AT_MOST時(shí),父view強(qiáng)加給子view一個(gè)最大的值庵佣。(一般是父view設(shè)置為wrap_content)  
        case MeasureSpec.AT_MOST:  
            // 道理同上  
            if (childDimension >= 0) {  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }  
            break;  
      
        // 當(dāng)父view的模式為UNSPECIFIED時(shí)歉胶,父容器不對(duì)view有任何限制,要多大給多大
        // 多見于ListView巴粪、GridView  
        case MeasureSpec.UNSPECIFIED:  
            if (childDimension >= 0) {  
                // 子view大小為子自身所賦的值  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                // 因?yàn)楦竩iew為UNSPECIFIED通今,所以MATCH_PARENT的話子類大小為0  
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED;  
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                // 因?yàn)楦竩iew為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0  
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED;  
            }  
            break;  
        }  
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    }  

image

其中的規(guī)律總結(jié):(以子View為標(biāo)準(zhǔn)肛根,橫向觀察)


image

三辫塌、measure過程

  • View measure過程

流程 measure()->onMeasure()->setMeasureDimension()->getDefaultSize()

/**
  * 源碼分析:measure()
  * 定義:Measure過程的入口;屬于View.java類 & final類型派哲,即子類不能重寫此方法
  * 作用:基本測(cè)量邏輯的判斷
  **/ 

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 參數(shù)說明:View的寬 / 高測(cè)量規(guī)格
    ...

    int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key);

    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 計(jì)算視圖大小 ->>分析1
    } else {
        ...
    }
}

/**
  * 分析1:onMeasure()
  * 作用:a. 根據(jù)View寬/高的測(cè)量規(guī)格計(jì)算View的寬/高值:getDefaultSize()
  *      b. 存儲(chǔ)測(cè)量后的View寬 / 高:setMeasuredDimension()
  **/ 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    // 參數(shù)說明:View的寬 / 高測(cè)量規(guī)格
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    // setMeasuredDimension() :獲得View寬/高的測(cè)量值 ->>分析2
    // 傳入的參數(shù)通過getDefaultSize()獲得 ->>分析3
}


/**
  * 分析2:setMeasuredDimension()
  * 作用:存儲(chǔ)測(cè)量后的View寬 / 高
  * 注:該方法即為我們重寫onMeasure()所要實(shí)現(xiàn)的最終目的
  **/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 
        //參數(shù)說明:測(cè)量后子View的寬 / 高值
        // 將測(cè)量后子View的寬 / 高值進(jìn)行傳遞
        mMeasuredWidth = measuredWidth;  
        mMeasuredHeight = measuredHeight;  
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
    } 
    // 由于setMeasuredDimension()的參數(shù)是從getDefaultSize()獲得的
    // 下面我們繼續(xù)看getDefaultSize()的介紹
}
/**
  * 分析3:getDefaultSize()
  * 作用:根據(jù)View寬/高的測(cè)量規(guī)格計(jì)算View的寬/高值
  **/
public static int getDefaultSize(int size, int measureSpec) {  

    // 參數(shù)說明:
    // size:提供的默認(rèn)大小
    // measureSpec:寬/高的測(cè)量規(guī)格(含模式 & 測(cè)量大辛狻)

        // 設(shè)置默認(rèn)大小
        int result = size; 
        
        // 獲取寬/高測(cè)量規(guī)格的模式 & 測(cè)量大小
        int specMode = MeasureSpec.getMode(measureSpec);  
        int specSize = MeasureSpec.getSize(measureSpec);  
      
        switch (specMode) {  
            // 模式為UNSPECIFIED時(shí),使用提供的默認(rèn)大小 = 參數(shù)Size
            case MeasureSpec.UNSPECIFIED:  
                result = size;  
                break;  

            // 模式為AT_MOST,EXACTLY時(shí)狮辽,使用View測(cè)量后的寬/高值 = measureSpec中的Size
            case MeasureSpec.AT_MOST:  
            case MeasureSpec.EXACTLY:  
                result = specSize;  
                break;  
        }  

     // 返回View的寬/高值
        return result;  
    }  
  • ViewGroup measure過程
    • 遍歷 測(cè)量所有子View的尺寸
    • 將所有子View的尺寸進(jìn)行合并,最終得到ViewGroup父視圖的測(cè)量值
    • measure->onMeasure(重寫)->measureChildren->measurechild->getChildMeasureSpec->遍歷子View測(cè)量巢寡、合并->setMeasureDimension->完成
    • viewGroup沒有默認(rèn)實(shí)現(xiàn),需要根據(jù)不同的需求進(jìn)行實(shí)現(xiàn)
/**
  * 根據(jù)自身的測(cè)量邏輯復(fù)寫onMeasure()喉脖,分為3步
  * 1. 遍歷所有子View & 測(cè)量:measureChildren()
  * 2. 合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測(cè)量值(自身實(shí)現(xiàn))
  * 3. 存儲(chǔ)測(cè)量后View寬/高的值:調(diào)用setMeasuredDimension()  
  **/ 

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  

    // 定義存放測(cè)量后的View寬/高的變量
    int widthMeasure ;
    int heightMeasure ;

    // 1. 遍歷所有子View & 測(cè)量(measureChildren())
    // ->> 分析1
    measureChildren(widthMeasureSpec, heightMeasureSpec);

    // 2. 合并所有子View的尺寸大小抑月,最終得到ViewGroup父視圖的測(cè)量值
     void measureCarson{
         ... // 自身實(shí)現(xiàn)
     }

    // 3. 存儲(chǔ)測(cè)量后View寬/高的值:調(diào)用setMeasuredDimension()  
    // 類似單一View的過程树叽,此處不作過多描述
    setMeasuredDimension(widthMeasure,  heightMeasure);  
}
// 從上可看出:
// 復(fù)寫onMeasure()有三步,其中2步直接調(diào)用系統(tǒng)方法
// 需自身實(shí)現(xiàn)的功能實(shí)際僅為步驟2:合并所有子View的尺寸大小


/**
  * 分析1:measureChildren()
  * 作用:遍歷子View & 調(diào)用measureChild()進(jìn)行下一步測(cè)量
  **/ 

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        // 參數(shù)說明:父視圖的測(cè)量規(guī)格(MeasureSpec)

        final int size = mChildrenCount;
        final View[] children = mChildren;

        // 遍歷所有子view
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
             // 調(diào)用measureChild()進(jìn)行下一步的測(cè)量 ->>分析1
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
}
/**
  * 分析2:measureChild()
  * 作用:a. 計(jì)算單個(gè)子View的MeasureSpec
  *      b. 測(cè)量每個(gè)子View最后的寬 / 高:調(diào)用子View的measure()
  **/ 
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {

    // 1. 獲取子視圖的布局參數(shù)
    final LayoutParams lp = child.getLayoutParams();

    // 2. 根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams谦絮,計(jì)算單個(gè)子View的MeasureSpec
    // getChildMeasureSpec() 請(qǐng)看上面第2節(jié)儲(chǔ)備知識(shí)處
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 獲取 ChildView 的 widthMeasureSpec
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 獲取 ChildView 的 heightMeasureSpec
            mPaddingTop + mPaddingBottom, lp.height);

    // 3. 將計(jì)算好的子View的MeasureSpec值傳入measure()题诵,進(jìn)行最后的測(cè)量
    // 下面的流程即類似單一View的過程洁仗,此處不作過多描述
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
image

簡(jiǎn)單說一下linearlayout的onMeasure流程,

  • 首先根據(jù)布局繪制方向走不同的邏輯

  • 如果是垂直方向性锭,拿到view總數(shù)后遍歷子view的高度并記錄

  • 得到所有子View的尺寸大小赠潦,最終得到ViewGroup的測(cè)量值

  • 將得到的view寬高記錄下來

  • 是否使用weight,如果使用,需要測(cè)量2遍草冈。

  • view的measure過程她奥,如果自定義viewGroup,需要重寫onMeasure(),這里主要有三個(gè)步驟,1:調(diào)用measureChilren()測(cè)量子view,2:根據(jù)邏輯把計(jì)算寬高得到父view的大小怎棱。3:存儲(chǔ)測(cè)量后父view的大小哩俭。
    在measureChildren中,循環(huán)測(cè)量所有view,根據(jù)父view的寬高拳恋、mode和自身的layoutparams凡资,得到每個(gè)子view的寬高和mode后調(diào)用單個(gè)view的measure流程。
    單個(gè)view的measure流程谬运,是在measure里調(diào)用onmeasure方法里計(jì)算view的寬高隙赁,并記錄。

  • view的layout過程吩谦,也先說一下viewGroup鸳谜,完成父view的位置測(cè)量,然后在onlayout中確定子view的位置式廷,然后循環(huán)遍歷子view咐扭,走view的layout過程,view的layout過程滑废,判斷自身的位置和大小是否改變蝗肪,如果改變了就調(diào)用onlayout方法重新調(diào)整位置。

  • draw總結(jié)蠕趁。先說一下view的draw()流程薛闪,本身draw里面分為幾個(gè)步驟,首先畫背景俺陋,然后ondraw畫自身的內(nèi)容豁延,然后分發(fā)畫子view(因?yàn)闆]有子view,所以是空實(shí)現(xiàn))腊状,最后畫裝飾诱咏,如滾動(dòng)和前景等。而viewGroup缴挖,主要是在onDraw方法中袋狞,實(shí)現(xiàn)自身的繪制邏輯,然后dispatchDraw,遍歷子view走單獨(dú)view的繪制流程苟鸯。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末同蜻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子早处,更是在濱河造成了極大的恐慌湾蔓,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陕赃,死亡現(xiàn)場(chǎng)離奇詭異卵蛉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)么库,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門傻丝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诉儒,你說我怎么就攤上這事葡缰。” “怎么了忱反?”我有些...
    開封第一講書人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵泛释,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我温算,道長(zhǎng)怜校,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任注竿,我火速辦了婚禮茄茁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巩割。我一直安慰自己裙顽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開白布宣谈。 她就那樣靜靜地躺著愈犹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闻丑。 梳的紋絲不亂的頭發(fā)上漩怎,一...
    開封第一講書人閱讀 50,043評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音嗦嗡,去河邊找鬼扬卷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛酸钦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼卑硫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼徒恋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起欢伏,我...
    開封第一講書人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤入挣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后硝拧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體径筏,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年障陶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滋恬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抱究,死狀恐怖恢氯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鼓寺,我是刑警寧澤勋拟,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站妈候,受9級(jí)特大地震影響敢靡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苦银,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一啸胧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墓毒,春花似錦吓揪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至主胧,卻和暖如春叭首,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踪栋。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來泰國打工焙格, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夷都。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓眷唉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冬阳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351