View的測量過程

簡述

android的界面顯示其實分成兩塊窿锉,一塊了系統(tǒng)的DecorView(頂級view)和普通view 膜蛔,而DecorView包含一個豎直的LinearLayout伞广,上部分是titleBar ,下部分是一個id為content的frameLayout轩拨,不管是顯示過程還是事件分發(fā)扩氢,都是由它傳遞而來。另一塊就是我們自己設置填充到DecorView中framelayout的view ≌ū埃現(xiàn)在是不是有點體會了既鞠,為什么在給activity設置布局的時候方法是setContentView() ,因為我們的確是把view設置到id為content的FrameLayout當中的矾兜。

不管是DecorView或者是普通view 损趋,要顯示到界面上都要經(jīng)歷三個過程:measure(測量寬高)layout(布局位置)椅寺、draw(繪制)


view的測量過程

View的測量過程決定了它的寬高浑槽,MeasureSpec參與了view的測量,所以我們有必要詳細了解一下

MeasureSpec

MeasureSpec代表了一個32位的int值返帕,高2位是SpecMode(測量模式)桐玻,低30位是測量SpecSize(測量尺寸),而SpecMode又分為三種荆萤,不同的模式下最終生成的尺寸是不一樣的

  • AT_MOST (最大模式):父容器指定一個可用的大小即SpecSize镊靴,子view的大小不可超過該尺寸,具體視圖子view的實現(xiàn)而定链韭,對應于wrap_content屬性

  • EXACTLY (精準模式):父容器已經(jīng)計算出了確定的值偏竟,子view的最終大小就是SpecSize的值,對應于match_parent和確定值

  • UNSPECIFIED (未指定):父容器不對子view做任何限制敞峭,需要多大就給多大踊谋,一般用于系統(tǒng)內(nèi)部,我們就不用太過關心了


現(xiàn)在我們大概了解了什么是MeasureSpec旋讹,那么它是怎樣生成的呢殖蚕?

對于DecorView轿衔,它的MeasureSpec由屏幕尺寸和自身的LayoutParamas決定。對于普通view睦疫,它的measure過程由ViewGroup的measureChild()調用

protected void measureChild(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {   
  final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    
  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);   
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

由此可見害驹,子view的measureSpec由父容器的MeasureSpec、padding/margin蛤育、自身layoutParamas決定宛官,具體實現(xiàn)由于代碼稍多就不貼出來了,有興趣可以自己研究缨伊≌蹋總結起來就是:

  • 子view的寬高是具體值进宝,SpecMode總是EXACTLY刻坊,最后的寬高就是設置的值

  • 子view的寬高為match_parent,則SpecMode(測量模式)由父容器決定党晋,最后的寬高取決于父容器的測量尺寸

  • 子view的寬高為wrap_content谭胚,則SpecMode(測量模式)始終是AT_MOST,最后的寬高不能超過父容器的剩余空間

View的measure過程

view的measure()方法是一個final方法未玻,里面調用的是onMeasure()灾而,如下:

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

setMeasureDimension()會把view的寬高測量設置進系統(tǒng),再來看看系統(tǒng)默認的處理測量的寬高的方法getDefaultSize()

public static int getDefaultSize(int size, int measureSpec) {    
   int result = size; 
    //獲取測量模式和測量值
   int specMode = MeasureSpec.getMode(measureSpec);
   int specSize = MeasureSpec.getSize(measureSpec);
   switch (specMode) {
    //這種模式主要用于系統(tǒng)的多次測量扳剿,我們不用關心
      case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    //在我們關心的這2中模式下旁趟,返回的其實就是測量值
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

這個方法的邏輯也很簡單,簡單來說就是直接返回了測量值(SpecSize)庇绽,當然這是系統(tǒng)默認的處理锡搜,我們在自定義view的時候可以重寫onMeasure()方法,根據(jù)自己的邏輯把測量值設置進去瞧掺。

額外補充一點耕餐,如果我們自定義view的時候默認系統(tǒng)設置測量值的方法,那么wrap_content的作用效果跟match_parent一樣辟狈,為什么肠缔?回看一下上面的總結,當view的寬高屬性值為wrap_content時哼转,測量模式為AT_MOST明未,測量尺寸為specSize,就是父容器的剩余可用尺寸壹蔓,這不就跟match_parent效果一樣了么趟妥!

那有什么方法能處理么?很簡單庶溶,重寫onMeasure()煮纵,當寬高屬性為wrap_content的時候懂鸵,給設置一個默認的大小,ok搞定

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬高的測量模式
    int withSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int withSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //當寬/高為wrap_content即對用AT_MOST的時候行疏,設置一個默認值給系統(tǒng)    
    if (withSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(defWith,defHeight);
    }else if (withSpecMode==MeasureSpec.AT_MOST){ 
       setMeasuredDimension(defWith,heightSpecSize);
    }else {
        setMeasuredDimension(withSpecSize,defHeight);
    }
}

ViewGroup的measure過程

對于ViewGroup本身匆光,系統(tǒng)并沒有提供一個默認的onMeasure方法,因為不同特性的ViewGroup(比如LinearLayoutRelativeLayout)他們的測量方式肯定是不一樣的酿联,所以需要子類自己去實現(xiàn)终息。而對于ViewGroup里面的view,則會通過measureChildren()循環(huán)遍歷每一個view贞让,然后調用measureChild()(這個方法的邏輯跟measureChildWithMargins()一模一樣)周崭,如此從而完成測量。

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);
        }
    }
}

measureChild()方法的核心是:取出子view的layoutParamas和自身的MeasureSpec生成MeasureSpec傳遞給子view的measure()方法喳张,完成對子view的測量

獲取View的測量寬/高

measure()方法完成后续镇,可以通過getMeasureHeight/with獲取測量的寬高,但是這時取出的值并不一定是最終的值销部,某些情況下系統(tǒng)會多次調用measure才能完成測量摸航。所以最準確的方式是在layout中獲取測量的寬高值


如果我們想在activity或者fragment中獲取一個view的測量寬高怎么辦?

  • 在activity中可在onWindowFocusChanged()方法里面獲取舅桩,缺點就是activity得到或者是去焦點時都會回調酱虎,可用導致頻繁的調用
@Overridepublic void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        etUsername.getMeasuredWidth();
    }
}
  • view.post(runnable)這種方法是極力推薦的,投遞一個runnable到消息隊列擂涛,當looper處理這條消息的時候读串,view也初始化好了
etUsername.post(new Runnable() {
    @Override    public void run() {
        etUsername.getMeasuredWidth();
    }
});
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撒妈,隨后出現(xiàn)的幾起案子恢暖,更是在濱河造成了極大的恐慌,老刑警劉巖踩身,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胀茵,死亡現(xiàn)場離奇詭異,居然都是意外死亡挟阻,警方通過查閱死者的電腦和手機琼娘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來附鸽,“玉大人脱拼,你說我怎么就攤上這事】辣福” “怎么了熄浓?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我赌蔑,道長俯在,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任娃惯,我火速辦了婚禮跷乐,結果婚禮上,老公的妹妹穿的比我還像新娘趾浅。我一直安慰自己愕提,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布皿哨。 她就那樣靜靜地躺著浅侨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪证膨。 梳的紋絲不亂的頭發(fā)上如输,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音椎例,去河邊找鬼挨决。 笑死,一個胖子當著我的面吹牛订歪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肆捕,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刷晋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慎陵?” 一聲冷哼從身側響起眼虱,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎席纽,沒想到半個月后捏悬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡润梯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年过牙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纺铭。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡寇钉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舶赔,到底是詐尸還是另有隱情扫倡,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布竟纳,位于F島的核電站撵溃,受9級特大地震影響疚鲤,放射性物質發(fā)生泄漏。R本人自食惡果不足惜缘挑,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一石咬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卖哎,春花似錦鬼悠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至维贺,卻和暖如春它掂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背溯泣。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工虐秋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垃沦。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓客给,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肢簿。 傳聞我的和親對象是個殘疾皇子靶剑,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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