Android View的Measure測量流程全解析

    相信絕大多數Android開發者都有自定義View來知足各類各樣需求的經歷,也知道一個View的繪製展現要通過measure、layout、draw三大流程,三者中measure的過程相比是稍微複雜一點點的。這篇文章做爲一個Android基礎的分享,分享一下view/viewGroup measure的過程,view/viewGroup是如何經過measure來肯定本身的寬高,最後經過自定義一個流式佈局來實踐一下。java

layout的過程本質上就是計算設定本身的座標或者本身child的座標,而draw須要畫布和畫筆以及提供的豐富的api來繪製你想要的效果。git

本文主要分爲如下三部分:github

  • 什麼是MeasureSpec,它的做用是什麼
  • measure過程是什麼樣的,它是如何肯定一個View/ViewGroup的寬高
  • 重寫onMeasure()方法來自定義一個流式佈局

1. 什麼是MeasureSpec,它的做用是什麼

MeasureSpec的做用

    咱們知道View是經過onMeasure()來肯定本身的寬高的(ViewGroup是個抽象類繼承自View,它並無重寫onMeasure(),因此若是自定義ViewGroup的時候沒有重寫onMeasure(),它最終調的仍是View的onMeasure()方法),那麼假定咱們沒有重寫onMeasure()方法,那麼View的寬高是怎樣的呢? View.onMeasure:api

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

顯然是經過setMeasureDimension()方法來肯定寬高的,來看一下這個方法:佈局

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ```
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
複製代碼
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        //賦值給寬高成員變量,寬高肯定,此時外界就能夠經過getMeasuredWidth()、getMeasuredHeight()來獲取View的寬高了
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
複製代碼

    經過以上代碼咱們能夠知道,View的onMeasure()裏經過setMeasuredDimension(int measuredWidth, int measuredHeight)方法就能夠設置View的寬高,那麼裏面的兩個寬高參數是怎麼來的呢?接着看getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec):spa

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        //取出MeasureSpec中的specMode和specSize
        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:
            //將specSize賦值給result
            result = specSize;
            break;
        }
        return result;
    }
複製代碼

    終於咱們的主角MeasureSpec登場了,從代碼能夠看出,最終設定的寬高也就是從MeasureSpec中取出的specSize。因此綜上所訴,在沒有重寫onMeasure()方法的狀況下,MeasureSpec就決定了View/ViewGroup的寬高。3d

MeasureSpec基礎知識

接下來介紹一些MeasureSpec的基礎知識,讓咱們更加了解什麼是MeasureSpec:code

從getDefaultSize方法中也能夠看出,MeasureSpec中包含兩部分,一個是SpecMode(測量模式),一個是SpecSize(某種測量模式下的規格大小)。它是用一個32位的int值來表示的,高2位代碼SpecMode,低30位表明SpecSize。orm

specMode主要分爲三種:cdn

模式 說明
EXACTLY 設置了精確的寬高。如width、height設置了具體值或者設置爲 match_parent,都屬於這種模式
AT_MOST width、height設置爲wrap_content則屬於這種模式
UNSPECIFIED 以上兩種模式是咱們佈局裏常見的,最大也不會大過父佈局,而這種模式通常用於系統, 父容器不對View有任何限制

MeasureSpec是如何生成的

    經過上面咱們知道了在不重寫onMeasure()的狀況下,一個View/ViewGroup的MeasureSpec就決定了這個View/ViewGroup的寬高,顯然這個MeasureSpec是這個View/ViewGroup的父容器在調用子View的measure()方法時傳進來的,也就是說一個View/ViewGroup的MeasureSpec是由其父容器生成的,那麼是怎麼生成的呢?裏面的SpecSize和SpecMode是由什麼決定的呢?

    這裏因爲代碼比較多就不貼了,父容器經過調ViewGroup中的getChildMeasureSpec()來生成子View的MeasureSpec。getChildMeasureSpec()中主要是經過父容器的MeasureSpec以及子Views設置的寬高來共同決定子View的MeasureSpec中的SpecMode和SpecSize。

getChildMeasureSpec()代碼裏的生成規則:

1.當子View的寬高設置的是具體數值時,顯然咱們能夠直接拿到子View的寬高,則子View寬高就肯定了,不用再去考慮父容器的SpecMode了,此時子View的SpecMode爲EXACTLY,SpecSize就是設置的寬高。

2.當子View的寬高設置的是match_parent, 則無論父容器的SpecMode是什麼模式,子View的SpecSize就等於父容器的寬高,而子View的SpecMode隨父容器的SpecMode。(這裏沒有考慮UNSPECIFIED模式,若是父容器是UNSPECIFIED模式,則子View SpecSize爲0,SpecMode爲UNSPECIFIED)

3.當子View的寬高設置的是wrap_content,由於這種狀況父容器實在不知道子View應該多寬多高,因此子View的SpecSize給的是父容器的寬高,也就是說只是給子View限制了一個最大寬高,而子View的SpecMode是AT_MOST模式。(這裏沒有考慮UNSPECIFIED模式,若是父容器是UNSPECIFIED模式,則子View SpecSize爲0,SpecMode爲UNSPECIFIED)。

    經過上面的解析咱們能夠知道,當你給一個View/ViewGroup設置寬高爲具體數值或者match_parent,它都能正確的顯示,可是若是你設置的是wrap_content,則默認顯示出來是其父容器的大小,若是你想要它正常的顯示爲wrap_content,則你就要本身重寫onMeasure()來本身計算它的寬高度並設置。因此咱們日常自定義View/ViewGroup的時候之因此要重寫onMeasure(),就是爲了能讓wrap_content達到效果。

2. measure過程是什麼樣的,它是如何肯定一個View/ViewGroup的寬高

    咱們知道,整個繪製流程是從ViewRootImpl類中performTraversals()開始的,這裏面分別執行performMeasure、performLayout、performDraw來完成整個繪製的三大流程。而三大流程都是至頂向下,今天這裏只說measure的過程。

    這裏以DecorView(根View)面放着一個ViewGroup(ViewGroupA)ViewGroup裏面放着一個View(ViewB)爲例來講明整個測量的流程:

1. ViewRootImpl.performTraversals()->performMeasure():

這裏面會調getRootMeasureSpec()根據手機屏幕的寬高和DecorView的LayoutParams生成DecorView的MeasureSpec,而後調用DecorView的measure()開始DecorView的測量

2.DecorView.measure()->onMeasure():

DecorView繼承自FrameLayout,因此會走到FrameLayout的onMeasure(),onMeasure()裏調measureChild()來根據上面說的規則爲ViewGroupA生成MeasureSpec,並經過ViewGroupA.measure()開始ViewGroupA的測量

3.ViewGroupA.measure()->onMeasure():

這是咱們自定義的一個ViewGroup(繼承自ViewGroup) 假如咱們沒有重寫onMeasure()的話,則默認調的是View.onMeasure(),則不會發起對子View的measure,它裏面的子View也就不會被測量(0),而這個ViewGroup若是沒有設置具體寬高的話,(wrap_content)則ViewGroup展現的就是父容器的寬高(根據上面說的MeasureSpec生成規則)。

    因此若是咱們繼承自ViewGroup來自定義一個ViewGroup的話,是確定要重寫onMeasure()的,裏面要調用measureChild()來爲子View生成MeasureSpec並調child.measure()開始對child的測量,這樣子View才能被測量顯示。而若是咱們要使設置的wrap_content生效,還要根據子View測量結果進行計算從而獲得本身的寬高,最後經過調setMeasuredDimension(int measuredWidth, int measuredHeight)來設置本身的寬高,從而達到wrap_content的效果。

4. ViewB.measure()->onMeasure():

View的測量相對於ViewGroup要簡單點,由於不用去Measure child,可是同樣的,若是要使wrap_conten生效需本身重寫onMeasure()計算。

3.重寫onMeasure()方法來自定義一個流式佈局

關於流式佈局,也叫自動換行佈局,一行放不下時會自動折行,效果以下:

具體實現地址:github.com/zhengcx/Lin…

    經過上面的measure流程分析,這個效果應該仍是比較好實現的,寬度測量上其實不須要咱們太關心,直接拿MeasureSpec中的SpecSize就好了,因此主要是高度上的測量,咱們要作的其實也是上面說的幾點:

1.根據規則爲子View生成MeasureSpec,並循環測量子View (ViewGroup.measureChild()裏作的事)

2.拿到測量好的子View的寬高,來計算本身wrap_content時應該呈現的寬高。像這個例子主要是根據子View的寬度來判斷是否須要折行,若是折行,則ViewGroup的高度也就隨之變大,總之須要去計算ViewGroup應該展現的高度。

3.經過調setMeasuredDimension(int measuredWidth, int measuredHeight)來設置本身的寬高。

具體實現見github:github.com/zhengcx/Lin…

相關文章
相關標籤/搜索