View的measure過(guò)程

普通View的measure過(guò)程

這里的普通View是指對(duì)應(yīng)ViewGroup而言的。View的measure過(guò)程是由其measure()方法來(lái)完成的此蜈,measure()方法是一個(gè)final類型的方法。這意味著子類不能重寫(xiě)此方法噪生。但是我們發(fā)現(xiàn)裆赵,View的measure()中會(huì)去調(diào)用View的onMeasure()方法,因此只需要看onMeasure的實(shí)現(xiàn)即可跺嗽,View的onMeasure的實(shí)現(xiàn)如下所示顾瞪。

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

注解一:
直接調(diào)用了setMeasuredDimension(int widthSize, int heightSize)方法,里面需要輸入兩個(gè)int形參抛蚁,分別是得到的寬和高的測(cè)量尺寸陈醒。上面代碼中,widthSize對(duì)應(yīng)getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),heightSize對(duì)應(yīng)著getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)瞧甩。那么getDefaultSize()又是什么樣的呢钉跷?

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;
      case MeasureSpec.AT_MOST:
      case MeasureSpec.EXACTLY:
            result = specSize;
            break;
}

從上面的代碼可以看出,getDefaultSize()這個(gè)方法邏輯很簡(jiǎn)單:

  • 如果該View的SpecMode是AT_MOST和EXACTLY兩種情況肚逸,則getDefaultSize()返回的數(shù)值爷辙,也就是該View測(cè)量后的對(duì)應(yīng)大斜蚧怠(寬或者是高),就是對(duì)應(yīng)的MeasureSpec中的SpecSize膝晾。
  • 如果該View的SpecMode是UNSPECIFIED栓始,這種情況一般是系統(tǒng)內(nèi)部測(cè)量過(guò)程,此時(shí)血当,getDefaultSiez()方法會(huì)返回第一個(gè)參數(shù)size幻赚,以寬為例,即getSuggestedMinimumWidth()方法臊旭。根據(jù)源碼落恼,該方法會(huì)返回值的邏輯:如果該View沒(méi)有設(shè)置背景,那么直接返回android:minWidth這個(gè)屬性所指定的值离熏,這個(gè)值可以為0佳谦;如果View設(shè)置了背景,則返回android:minWidth和背景的最小寬度兩者中的最大值滋戳。

上面的過(guò)程就是普通View的Measure過(guò)程钻蔑,可以看出View的寬/高就是specSize決定的。同時(shí)奸鸯,假如該View的布局屬性是wrap_content咪笑,那么最后設(shè)置的測(cè)量后寬/高也為specSize。這種情況就和我們預(yù)想的不一樣了府喳。如果一個(gè)View的布局屬性是wrap_content蒲肋,那么它的測(cè)量寬/高應(yīng)該是大于小于specSize,但是這是卻直接設(shè)置為specSize钝满。所以直接繼承View的自定義控件需要重寫(xiě)onMeasure()方法兜粘,設(shè)置wrap_content情況下的自身大小,否則wrap_content屬性就和match_parent屬性相同了弯蚜。重寫(xiě)的方式如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);             //先按照父類的方法計(jì)算孔轴,下面就行覆蓋
      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec):
      int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec):
      int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec):
      int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec):

      if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,mHeight);                     //注解二
      } else if (widthSpecMode == AT_MOST) {
            setMeasureDimension(mWidth,heightSpecSize);
      } else if (heightSpecMode == AT_MOST){
            setMeasureDimension(widthSpecSize,mHeight);
      }
}

注解二:
這里我們提前給View指定了一個(gè)默認(rèn)的內(nèi)部寬/高(mWidth,mHeight),并在wrap_content時(shí)設(shè)置此寬/高碎捺。同時(shí)路鹰,這里的mWidth和mHeight需要靈活指定,這樣才和和wrap_content屬性相符合收厨。

ViewGroup的measure過(guò)程

ViewGroup除了需要完成自己的measure過(guò)程外晋柱,還需要遍歷所有的子元素的measure方法,各個(gè)子元素去遞歸執(zhí)行這個(gè)過(guò)程诵叁。和View不同的是雁竞,ViewGroup是一個(gè)抽象類,因此它沒(méi)有重寫(xiě)View的onMeasure()方法(在繼承ViewGroup的類中,比如LinearLayout碑诉,一般都重寫(xiě)了這個(gè)方法)彪腔,但是提供了一個(gè)measureChildren()的方法,如下:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec){
      final int size = mChildrenCount;
      final View[] children = mChildren;
      for(int i = 0; i < size; ++i){
            final View child = children[i];
            if ( (child.mViewFlags & VISIBILITY_MASK) != GONE){
                  measureChild(child, widthMeasureSpec,heightMeasureSpec);
            }
      }
}

從上面的代碼可以看出进栽,ViewGroup通過(guò)measureChildren()方法中for循環(huán)德挣,對(duì)它所有的Visibility屬性不等于GONE的子View執(zhí)行measureChild()方法,measureChild()方法的實(shí)現(xiàn)如下:

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasure){
      final LayoutParams lp = child.getLayoutParams();
      
      final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight,lp.width);
      final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom,lp.height);

      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

上面的代碼的思路很清晰快毛,首先通過(guò)getChildMeasureSpec()方法獲得子View的MeasureSpec格嗅,然后調(diào)用子View的onMeasure()方法。通過(guò)上面的方法祸泪,就可以包裝ViewGroup中所有的子View的會(huì)被測(cè)量(measure)吗浩。
但是建芙,ViewGroup并沒(méi)有定義其測(cè)量的具體過(guò)程没隘,即沒(méi)有重寫(xiě)其父類View的measure()方法。這是因?yàn)閂iewGroup是一個(gè)抽象類禁荸,不同的ViewGroup子類有不同的布局特性右蒲,所以需要這些子類自己去實(shí)現(xiàn)onMeasure()方法。不同的布局屬性赶熟,實(shí)現(xiàn)方式不同瑰妄。但是總體思路和View的onMeasure()方法是一樣的,根據(jù)ViewGroup的不同SpecMode(UNSPECIFIED,AT_MOST,EXACTLY)來(lái)設(shè)置不同的測(cè)量值映砖。

補(bǔ)充

上面已經(jīng)對(duì)View的measure過(guò)程進(jìn)行了詳細(xì)的分析间坐,現(xiàn)在考慮一種情況,比如我們想在Acitivity已啟動(dòng)的時(shí)候獲取某個(gè)View的寬/高邑退,這時(shí)候應(yīng)該怎么做竹宋?如果直接在onCreate()或者onResume()方法里面去獲取這個(gè)View的寬/高,是無(wú)法正確得到某個(gè)View的寬/高信息地技,這是因?yàn)閂iew的measure過(guò)程和Activity的生命周期不是同步執(zhí)行的蜈七,因此無(wú)法保證Activity執(zhí)行了onCreate(),onStart(),onResume()時(shí)某個(gè)View已經(jīng)測(cè)量完畢了,如果View沒(méi)有測(cè)量完畢莫矗,那么獲得的寬/高數(shù)據(jù)就是0飒硅。這里給出下面的四種方法:

  1. Activity中的onWindowFocusChanged()方法
    這個(gè)方法意味著:View已經(jīng)初始化完畢了,寬/高都已經(jīng)準(zhǔn)備好了作谚。但是三娩,onWindowFocusChanged()方法會(huì)被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)或者失去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次妹懒。具體來(lái)說(shuō)雀监,就是onResume()onPause()方法執(zhí)行時(shí),onWindowFocusChanged()方法就會(huì)被調(diào)用彬伦。典型代碼如下:
public void onWindowFocusChanged(boolean hasFocus){
      super.onWindowFocusChanged(hasFocus);
      if(hasFocus){
            int width = view.getMeasureWidth();
            int height = view.getMeasureHeight();
      }
}
  1. 使用view.post(runnable)方法
    通過(guò)post方法可以將一個(gè)runnable投遞到消息隊(duì)列的尾部滔悉,然后等待Looper調(diào)用此runnable的時(shí)候伊诵,View也已經(jīng)初始化好了。典型代碼如下:
protected void onStart(){
      super.onStart();
      view.post(new Runnable(){
            @override
            public void run(){
                  int width = view.getMeasuredWidth();
                  int height = view.getMeasuredHeight();     
            }
      });
}
  1. ViewTreeObserver
    使用ViewTreeOberser的眾多回調(diào)可以完成這個(gè)功能回官。比如使用OnGlobalLayoutListener這個(gè)接口曹宴,當(dāng)View樹(shù)的狀態(tài)發(fā)生改變或View樹(shù)內(nèi)部的View的可見(jiàn)性發(fā)生改變時(shí),OnGlobalLayoutListener接口中的onGlobalLayout()方法將被回調(diào)歉提。但是笛坦,隨著View樹(shù)的狀態(tài)改變等,onGlobalLayout()方法將被多次嗲用苔巨。典型代碼如下:
protected void onStart(){
      super.onStart();
      
      ViewTreeObserver observer = view.getViewTreeObserver();
      observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
            @SuppressWarnings("deprecation");
            @override
            public void onGlobalLayout(){
                  view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                  int width = view.getMeasuredWidth();
                  int height = view.getMeasuredHeight();
            }
      });
}
  1. 通過(guò)view.measure(int widthMeasureSpec,int heightMeasureSpec)
    通過(guò)手動(dòng)對(duì)View進(jìn)行measure來(lái)得到寬/高版扩,這種方法需要根據(jù)View的LayoutParams
  • match_parent 無(wú)法知道parentSize,所以這種情況無(wú)法measure出具體的寬/高
  • 具體數(shù)值:直接measure
  • wrap_content:MeasureSpec= (wrap_content) + 11111...1(最大值)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侄泽,一起剝皮案震驚了整個(gè)濱河市礁芦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悼尾,老刑警劉巖柿扣,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闺魏,居然都是意外死亡未状,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)析桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)司草,“玉大人,你說(shuō)我怎么就攤上這事泡仗÷窈纾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵沮焕,是天一觀的道長(zhǎng)吨岭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)峦树,這世上最難降的妖魔是什么辣辫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮魁巩,結(jié)果婚禮上急灭,老公的妹妹穿的比我還像新娘。我一直安慰自己谷遂,他們只是感情好葬馋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般畴嘶。 火紅的嫁衣襯著肌膚如雪蛋逾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天窗悯,我揣著相機(jī)與錄音区匣,去河邊找鬼。 笑死蒋院,一個(gè)胖子當(dāng)著我的面吹牛亏钩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欺旧,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼姑丑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了辞友?” 一聲冷哼從身側(cè)響起栅哀,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踏枣,沒(méi)想到半個(gè)月后昌屉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钙蒙,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茵瀑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躬厌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片马昨。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扛施,靈堂內(nèi)的尸體忽然破棺而出鸿捧,到底是詐尸還是另有隱情,我是刑警寧澤疙渣,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布匙奴,位于F島的核電站,受9級(jí)特大地震影響妄荔,放射性物質(zhì)發(fā)生泄漏泼菌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一啦租、第九天 我趴在偏房一處隱蔽的房頂上張望哗伯。 院中可真熱鬧,春花似錦篷角、人聲如沸焊刹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)虐块。三九已至俩滥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贺奠,已是汗流浹背举农。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敞嗡,地道東北人颁糟。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喉悴,于是被迫代替她去往敵國(guó)和親棱貌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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