自定義 view - 測(cè)量 onMeasure

自定義 view 的3個(gè)核心方法

  • onMeasure
    根據(jù) view 的測(cè)量模式計(jì)算確定 view 的寬高
  • onLayout
    ViewGroup 中對(duì)所有的子 view 排版膀跌,決定子 view 的位置
  • onDraw
    具體繪制 view

本節(jié)我們來說說 onMeasure 盔几,view 的寬高的大小如何決定


自定義 View 繪制流程

005Xtdi2jw1f638wreu74j30fc0heaay.jpg

看完上面的圖辽俗,那么今天我們呢就來說說 view 的測(cè)量 onMeasure


onMeasure 的經(jīng)典寫法

在自定義 view 的 onMeasure 測(cè)量方法中,所有的資料都是建議下面這種經(jīng)典寫法

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

    // 獲取寬的測(cè)量模式
    int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
    // 獲取符控件提供的 view 寬的最大值
    int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);

    int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, 300);
    } else if (wSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, hSpecSize);
    } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
    }
}

先不要問為什么,先熟悉下代碼,一會(huì)會(huì)有用到


view 的測(cè)量模式

view 的測(cè)量涉及到一個(gè)重要的點(diǎn),測(cè)量模式驶兜,view 有3個(gè)測(cè)量模式,看下圖:


944365-e631b96ea1906e34.png
  • USPENCIFIED
    是不限制子 view 大小的远寸,我們?cè)谧远x view 時(shí)用不到抄淑,一般也不用處理

  • EXACTLY :精準(zhǔn)模式
    當(dāng) view 的寬高設(shè)置為 MATCH_PARENT 或者固定大小時(shí),view 的測(cè)量就是 EXACTLY 類型的驰后。

  • AT_MOST :最大值模式
    當(dāng) view 的寬高設(shè)置為 WARP_CONTENT 時(shí)肆资,ew 的測(cè)量就是 AT_MOST 類型的。這是我們需要返回給系統(tǒng)一個(gè)值倡怎,已通知系統(tǒng)這個(gè) view 的寬高應(yīng)該是多少迅耘,若是我們不做處理贱枣,那么就會(huì)按 EXACTLY 測(cè)量,最大值不會(huì)查過父控件的寬高

清楚了這3個(gè)模式颤专,尤其是 EXACTLY 和 EXACTLY 代表什么意思之后纽哥,我們要來說一說這個(gè)測(cè)量模式了。

測(cè)量模式對(duì)應(yīng)的 matchParent栖秕,warp_content春塌,具體寬高數(shù)值,都是寫在 android:layout_width簇捍,android:layout_height xml 屬性里面的只壳,注意這都是 layout 的 xml 標(biāo)簽, layout 的 xml 標(biāo)簽最終都會(huì)把數(shù)據(jù)寫入到 LayoutParams 里面暑塑,LayoutParams 是給 view 的父控件 ViewGorup 用的吼句,父控件解析所有子 view 的 LayoutParams 參數(shù),然后把這些參數(shù)經(jīng)過處理包裝到 widthMeasureSpec事格,heightMeasureSpec 里面?zhèn)鬟f給自子 view 的惕艳,自定義 view onMeasure 方法里面的參數(shù)就是這么來的。

這就帶出一個(gè)問題驹愚,一個(gè) view 的寬高不僅僅是自己決定的远搪,也是父控件決定的。一個(gè) view 先估算自己的寬高逢捺,然后告知父控件谁鳍,父控件再最終決定view 的寬高是多少。這里面起決定作用的還是 view 自己的估算劫瞳,父控件只是做一個(gè)最終的上限復(fù)核倘潜,子 view 的大小不嗯呢乖超過父控件的,這下大家都懂了吧


view 自己估算自家的寬高

還是上面哪段經(jīng)典代碼志于,我們放出來窍荧,方便觀看

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

    // 獲取寬的測(cè)量模式
    int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
    // 獲取符控件提供的 view 寬的最大值
    int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);

    int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, 300);
    } else if (wSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(300, hSpecSize);
    } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
    }
}

在上面的經(jīng)典代碼中,我們其實(shí)干了幾件很簡(jiǎn)單的事:

  1. 拿到 view 寬高的測(cè)量模式和父控件允許 view 寬高的最大值

  2. 根據(jù) view 寬高不同的測(cè)量模式區(qū)分對(duì)待

  3. 當(dāng)寬和高是 EXACTLY 時(shí)恨憎,表示 寬高已經(jīng)設(shè)定了一個(gè)固定值,match_parent 其實(shí)也是表示6個(gè)固定值郊楣,就是用父控件允許 view 寬高的最大值(符空間在寬高方向上剩余的最大值)憔恳,就是一個(gè)具體的,父控件返回給我們什么值净蚤,我們用什么值就行了钥组。當(dāng)我們?cè)?xml 中給寬高設(shè)置一個(gè)具體的值時(shí),比如 35dp今瀑,那么父控件就會(huì)把 35dp 返回給我們程梦,而不是 match_parent 時(shí)返回給我們?cè)试S的最大值点把,這點(diǎn)區(qū)別注意下

  4. onMeasure 里面最值得我們費(fèi)腦子的就是 AT_MOST 了,當(dāng) AT_MOST 出現(xiàn)時(shí)屿附,就表示我們給寬高使用了 warp_content郎逃,這個(gè)時(shí)候父控件是不知道子 view 寬高應(yīng)該是多少的,就需要子 view 明確聲明自己的寬高是多少了挺份。一個(gè)典型的例子褒翰,textview 就是根據(jù)文字矩陣的寬高來計(jì)算具體寬高的值的,但是自定義 view 的寬和高同時(shí)是 warp_content 是不多的匀泊,多數(shù)時(shí)都是寬是 match_parent 的优训,高是 warp_content 的,我們?cè)谠O(shè)計(jì)一個(gè)自定義 view 時(shí)各聘,自定義 view 圖案的寬高總是成比例的揣非,這樣我們就可以根據(jù)寬高一方的具體值按比例計(jì)算出寬高中的另一個(gè)值。但是我們碰到了寬高都是 warp_content 時(shí)呢躲因,我們根據(jù)寬高的比例和父控件寬高允許的最大值計(jì)算出 子 view 不出父控件時(shí)成比例的寬高值早敬。warp_content 時(shí)我們計(jì)算寬高值的基礎(chǔ)就是 自定義 view 的圖案必須成比例,要不誰知道這個(gè) view 應(yīng)該有多大呢毛仪。

  5. 最后用 setMeasuredDimension 通知父控件子 view 的大小搁嗓。

基本上自定義 view 計(jì)算自己的寬高就是這么搞的,說的簡(jiǎn)單箱靴,但是碰到 warp_content 時(shí)計(jì)算真的不是很輕松腺逛。


Margin 和 padding 的問題

這個(gè)我們根據(jù)需要處理

  • Margin
    外邊局的處理很簡(jiǎn)單,Margin 是會(huì)寫到 LayoutParams 里面的衡怀,在邏輯上都是交給父控件 ViewGroup 在 onLayout 方法中處理的

  • padding
    內(nèi)邊距就得我們自己在測(cè)量中處理了棍矛。我們使用這幾個(gè) api 就能在 view 中拿到 padding 的大小,getPaddingLeft() 抛杨、getPaddingRight 够委、getPaddingTop() 、getPaddingBottom()怖现,然后把的 padding 數(shù)值加到 view 的寬高里面去茁帽,不難處理,詳細(xì)處理可以看篇文章:


onSizeChange

onSizeChange 方法很好理解屈嗤,在 view 大小改變時(shí)會(huì)調(diào)用潘拨,我們來看看這個(gè)方法

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
}

四個(gè)參數(shù),分別為 寬度饶号,高度铁追,上一次寬度,上一次高度茫船,我們只需關(guān)注 寬度(w), 高度(h) 即可琅束,這兩個(gè)參數(shù)才是 View 的最終大小

onSizeChange 可能會(huì)多次觸發(fā)扭屁,view 會(huì)緩存上一次的大小,在 view 的大小改變時(shí)就會(huì)觸發(fā)這個(gè)回調(diào)了涩禀。觸發(fā) onSizeChange 回調(diào)的方法挺多料滥,比如 settop ,setleft 這類改變 view 的大小方法, addView埋泵,removeview 也會(huì)觸發(fā) onSizeChange


父控件 ViewGroup 對(duì)子 view 測(cè)量的影響

ViewGroup相當(dāng)于一個(gè)放置View的容器幔欧,并且我們?cè)趯懖季謝ml的時(shí)候,會(huì)告訴容器(凡是以layout為開頭的屬性丽声,都是為用于告訴容器的)礁蔗,我們的寬度(layout_width)、高度(layout_height)雁社、對(duì)齊方式(layout_gravity)等浴井;當(dāng)然還有margin等;于是乎霉撵,ViewGroup的職能為:給childView計(jì)算出建議的寬和高和測(cè)量模式 磺浙;決定childView的位置;為什么只是建議的寬和高徒坡,而不是直接確定呢撕氧,別忘了childView寬和高可以設(shè)置為wrap_content,這樣只有childView才能計(jì)算出自己的寬和高喇完。

然后 view 根據(jù)測(cè)量模式和ViewGroup給出的建議的寬和高伦泥,計(jì)算出自己的寬和高;同時(shí)還有個(gè)更重要的職責(zé)是:在ViewGroup為其指定的區(qū)域內(nèi)繪制自己的形態(tài)锦溪。

這里面最重要的點(diǎn)是 view 的測(cè)量模式不是自己解析出來的不脯,是父控件 ViewGroup 傳遞給子 view 的,父控件 ViewGroup 傳遞給子 view 之前刻诊,是經(jīng)過處理的防楷,這個(gè)處理我們需要清楚,不難则涯,實(shí)際頁沒啥用复局,但是我們得知道。另外這一段是我摘抄過來的粟判,看著有不通順的地方大家腦補(bǔ)一下就都能理解了肖揣。

ViewGroup的測(cè)量過程主要用到了三個(gè)方法:

  • measureChildren()
    遍歷所有的childView
  • getChildMeasureSpec()
    確定測(cè)量規(guī)格
  • measureChild()調(diào)用測(cè)量規(guī)格

measureChildren() 調(diào)用了 measureChild() ,measureChild() 又調(diào)用了 getChildMeasureSpec()浮入,getChildMeasureSpec() 是核心,需要看一下的羊异,看過我們就可以知道 view 的測(cè)量模式不僅收自己影響事秀,還受到父控件影響

/**
  * 源碼分析: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);  
        }

xml 中所有 layout 的屬性都不是給 view 看的弟翘,是給 view 的父控件 ViewGorup 看的,layout 的參數(shù)封裝在 LayoutParams 里面骄酗。ViewGorup 父控件遍歷所有的子 view 的測(cè)量模式稀余,然后結(jié)合自身測(cè)量模式,決定子 view 的測(cè)量模式及傳給子 view 的寬高建議值

在上面代碼中我們可以看到:

  • 當(dāng)父控件的測(cè)量模式是 EXACTLY 精確值時(shí)趋翻,子 view 在 layout 中設(shè)置的是什么測(cè)量模式就是什么測(cè)量模式睛琳。
  • 當(dāng)父控件的測(cè)量模式是 AT_MOST 包括內(nèi)容時(shí),即便子 view 在 layout 中設(shè)置的是 MATCH_PARENT 嘿歌,父控件也會(huì)把子 view 的測(cè)量模式設(shè)置為 AT_MOST
  • 父控件返回給子 view 寬高的建議值時(shí)掸掏,除了子 view 在 layout 中聲明了一個(gè)確切的數(shù)時(shí),返回這給子 view 這個(gè)確切的數(shù)宙帝,其實(shí)都是返回的父控件最大的寬高值

父控件寬高是 AT_MOST 時(shí)丧凤,父控件也不知道自己的寬高應(yīng)該是多少,這時(shí)他要依賴子 view 的大小才能確定自己的寬高步脓,所以會(huì)給子 view 設(shè)置成 AT_MOST 的測(cè)量模式愿待,就是希望子 view 明確zi view 自己具體的大小,以便父控件計(jì)算自己的大小靴患。所以我們?cè)?自定義 view 碰到 AT_MOST 時(shí)仍侥,就當(dāng) warp_content 處理就行了,經(jīng)典的 onMeasure 方法是久經(jīng)考驗(yàn)的

onMearsu 還有更多詳細(xì)的東西鸳君,比如源碼分析农渊,搭建看我下面提供的鏈接吧,如果你有興趣額的話:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末或颊,一起剝皮案震驚了整個(gè)濱河市砸紊,隨后出現(xiàn)的幾起案子传于,更是在濱河造成了極大的恐慌,老刑警劉巖醉顽,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沼溜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡游添,警方通過查閱死者的電腦和手機(jī)系草,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唆涝,“玉大人找都,你說我怎么就攤上這事∈眨” “怎么了檐嚣?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)啰扛。 經(jīng)常有香客問我嚎京,道長(zhǎng),這世上最難降的妖魔是什么隐解? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任鞍帝,我火速辦了婚禮,結(jié)果婚禮上煞茫,老公的妹妹穿的比我還像新娘帕涌。我一直安慰自己,他們只是感情好续徽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚓曼。 她就那樣靜靜地躺著,像睡著了一般钦扭。 火紅的嫁衣襯著肌膚如雪纫版。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天客情,我揣著相機(jī)與錄音其弊,去河邊找鬼。 笑死膀斋,一個(gè)胖子當(dāng)著我的面吹牛梭伐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仰担,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼糊识,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起技掏,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤铃将,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哑梳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绘盟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年鸠真,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龄毡。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吠卷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沦零,到底是詐尸還是另有隱情祭隔,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布路操,位于F島的核電站疾渴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屯仗。R本人自食惡果不足惜搞坝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魁袜。 院中可真熱鬧桩撮,春花似錦、人聲如沸峰弹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞠呈。三九已至融师,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粟按,已是汗流浹背诬滩。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灭将,地道東北人疼鸟。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像庙曙,于是被迫代替她去往敵國(guó)和親空镜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355