自定義View2---View Measure過程

移步自定義View系列

內容

  • 作用
  • 知識儲備
    • ViewGroup.LayoutParams
    • MeasureSpecs
    • 自定義View基礎
  • measure過程解析
    • 單一view
    • ViewGroup

1 作用

測量View的寬 / 高

    1. 在某些情況下,需要多次測量(measure)才能確定View最終的寬/高;
    1. 該情況下,measure過程后得到的寬 / 高可能不準確;
    1. 此處建議:在layout過程中onLayout()去獲取最終的寬 / 高

2 儲備知識

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

  • ViewGroup.LayoutParams類()
  • MeasureSpecs 類(父視圖對子視圖的測量要求)

2.1 ViewGroup.LayoutParams

  • 簡介
    布局參數類
  1. ViewGroup 的子類(RelativeLayout、LinearLayout)有其對應的 ViewGroup.LayoutParams 子類
  2. 如:RelativeLayout的 ViewGroup.LayoutParams子類
    = RelativeLayoutParams
  • 作用
    指定視圖View 的高度(height) 和 寬度(width)等布局參數。可
  • 具體使用
    通過以下參數指定
參數 解釋
具體值 dp / px
fill_parent 強制性使子視圖的大小擴展至與父視圖大小相等(不含 padding )
match_parent 與fill_parent相同,用于Android 2.3 & 之后版本
wrap_content 自適應大小,強制性地使視圖擴展以便顯示其全部內容(含 padding )
android:layout_height="wrap_content"   //自適應大小  
android:layout_height="match_parent"   //與父視圖等高  
android:layout_height="fill_parent"    //與父視圖等高  
android:layout_height="100dip"         //精確設置高度值為 100dip

2.2 MeasureSpec

2.2.1 簡介

image

2.2.2 組成

測量規格(MeasureSpec) = 測量模式(mode) + 測量大小(size)


image

其中,測量模式(Mode)的類型有3種:UNSPECIFIED、EXACTLY 和
AT_MOST。具體如下:

image

2.2.3 具體使用

  • MeasureSpec 被封裝在View類中的一個內部類里:MeasureSpec類
  • MeasureSpec類 用1個變量封裝了2個數據(size,mode):通過使用二進制,將測量模式(mode) & 測量大小(size)打包成一個int值來,并提供了打包 & 解包的方法

該措施的目的 = 減少對象內存分配

  • 實際使用
/**
  * MeasureSpec類的具體使用
  **/

    // 1. 獲取測量模式(Mode)
    int specMode = MeasureSpec.getMode(measureSpec)

    // 2. 獲取測量大小(Size)
    int specSize = MeasureSpec.getSize(measureSpec)

    // 3. 通過Mode 和 Size 生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
  • 源碼分析
/**
  * MeasureSpec類的源碼分析
  **/
    public class MeasureSpec {

        // 進位大小 = 2的30次方
        // int的大小為32位,所以進位30位 = 使用int的32和31位做標志位
        private static final int MODE_SHIFT = 30;  
          
        // 運算遮罩:0x3為16進制,10進制為3,二進制為11
        // 3向左進位30 = 11 00000000000(11后跟30個0)  
        // 作用:用1標注需要的值,0標注不要的值。因1與任何數做與運算都得任何數、0與任何數做與運算都得0
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  
        // UNSPECIFIED的模式設置:0向左進位30 = 00后跟30個0,即00 00000000000
        // 通過高2位
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
        
        // EXACTLY的模式設置:1向左進位30 = 01后跟30個0 ,即01 00000000000
        public static final int EXACTLY = 1 << MODE_SHIFT;  

        // AT_MOST的模式設置:2向左進位30 = 10后跟30個0,即10 00000000000
        public static final int AT_MOST = 2 << MODE_SHIFT;  
  
        /**
          * makeMeasureSpec()方法
          * 作用:根據提供的size和mode得到一個詳細的測量結果嗎,即measureSpec
          **/ 
            public static int makeMeasureSpec(int size, int mode) {  
            
                return size + mode;  
            // measureSpec = size + mode;此為二進制的加法 而不是十進制
            // 設計目的:使用一個32位的二進制數,其中:32和31位代表測量模式(mode)、后30位代表測量大小(size)
            // 例如size=100(4),mode=AT_MOST,則measureSpec=100+10000...00=10000..00100  

            }  
      
        /**
          * getMode()方法
          * 作用:通過measureSpec獲得測量模式(mode)
          **/    

            public static int getMode(int measureSpec) {  
             
                return (measureSpec & MODE_MASK);  
                // 即:測量模式(mode) = measureSpec & MODE_MASK;  
                // MODE_MASK = 運算遮罩 = 11 00000000000(11后跟30個0)
                //原理:保留measureSpec的高2位(即測量模式)、使用0替換后30位
                // 例如10 00..00100 & 11 00..00(11后跟30個0) = 10 00..00(AT_MOST),這樣就得到了mode的值

            }  
        /**
          * getSize方法
          * 作用:通過measureSpec獲得測量大小size
          **/       
            public static int getSize(int measureSpec) {  
             
                return (measureSpec & ~MODE_MASK);  
                // size = measureSpec & ~MODE_MASK;  
               // 原理類似上面,即 將MODE_MASK取反,也就是變成了00 111111(00后跟30個1),將32,31替換成0也就是去掉mode,保留后30位的size  
            } 

    }

2.2.6 MeasureSpec值的計算

  • 結論:子View的MeasureSpec值根據子View的布局參數(LayoutParams)和父容器的MeasureSpec值計算得來的,具體計算邏輯封裝在getChildMeasureSpec()里。


    image

    即:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定

  • 下面,我們來看getChildMeasureSpec()的源碼分析:
/**
  * 源碼分析:getChildMeasureSpec()
  * 作用:根據父視圖的MeasureSpec & 布局參數LayoutParams,計算單個子View的MeasureSpec
  * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
  **/

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  

         //參數說明
         * @param spec 父view的詳細測量值(MeasureSpec) 
         * @param padding view當前尺寸的的內邊距和外邊距(padding,margin) 
         * @param childDimension 子視圖的布局參數(寬/高)

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

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


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

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

                // 當子view的LayoutParams為WRAP_CONTENT時(-2)      
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    //子view決定自己的大小,但最大不能超過父view,模式為AT_MOST  
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  
          
            // 當父view的模式為AT_MOST時,父view強加給子view一個最大的值。(一般是父view設置為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;  
          
            // 當父view的模式為UNSPECIFIED時,父容器不對view有任何限制,要多大給多大
            // 多見于ListView、GridView  
            case MeasureSpec.UNSPECIFIED:  
                if (childDimension >= 0) {  
                    // 子view大小為子自身所賦的值  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                    // 因為父view為UNSPECIFIED,所以MATCH_PARENT的話子類大小為0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    // 因為父view為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0  
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
            }  
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
        }

如下表


image

其中的規律總結:(以子View為標準,橫向觀察)


image

由于UNSPECIFIED模式適用于系統內部多次measure情況,很少用到,故此處不討論


  • 區別于頂級View(即DecorView)的測量規格MeasureSpec計算邏輯:取決于 自身布局參數 & 窗口尺寸


    image

measure過程詳解

measure過程 根據View的類型分為2種情況:


image

3.1 單一View的measure過程

  • 應用場景
    在無現成的控件View滿足需求、需自己實現時,則使用自定義單一View
  1. 如:制作一個支持加載網絡圖片的ImageView控件
  2. 注:自定義View在多數情況下都有替代方案:圖片 / 組合動畫,但二者可能會導致內存耗費過大,從而引起內存溢出等問題。
  • 具體使用
    繼承自View、SurfaceView 或 其他View;不包含子View
  • 具體流程


    image

下面我將一個個方法進行詳細分析:入口 = measure()

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

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        // 參數說明:View的寬 / 高測量規格

        ...

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

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

        } else {
            ...
      
    }

/**
  * 分析1:onMeasure()
  * 作用:a. 根據View寬/高的測量規格計算View的寬/高值:getDefaultSize()
  *      b. 存儲測量后的View寬 / 高:setMeasuredDimension()
  **/ 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    // 參數說明:View的寬 / 高測量規格

    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    // setMeasuredDimension() :獲得View寬/高的測量值 ->>分析2
    // 傳入的參數通過getDefaultSize()獲得 ->>分析3
}

/**
  * 分析2:setMeasuredDimension()
  * 作用:存儲測量后的View寬 / 高
  * 注:該方法即為我們重寫onMeasure()所要實現的最終目的
  **/
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  

    //參數說明:測量后子View的寬 / 高值

        // 將測量后子View的寬 / 高值進行傳遞
            mMeasuredWidth = measuredWidth;  
            mMeasuredHeight = measuredHeight;  
          
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
        } 
    // 由于setMeasuredDimension()的參數是從getDefaultSize()獲得的
    // 下面我們繼續看getDefaultSize()的介紹

/**
  * 分析3:getDefaultSize()
  * 作用:根據View寬/高的測量規格計算View的寬/高值
  **/
  public static int getDefaultSize(int size, int measureSpec) {  

        // 參數說明:
        // size:提供的默認大小
        // measureSpec:寬/高的測量規格(含模式 & 測量大小)

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

                // 模式為AT_MOST,EXACTLY時,使用View測量后的寬/高值 = measureSpec中的Size
                case MeasureSpec.AT_MOST:  
                case MeasureSpec.EXACTLY:  
                    result = specSize;  
                    break;  
            }  

         // 返回View的寬/高值
            return result;  
        }
  • 上面提到,當模式是UNSPECIFIED時,使用的是提供的默認大小(即第一個參數size);那么,提供的默認大小具體是多少呢?
  • 答:在onMeasure()方法中,getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)中傳入的默認大小是getSuggestedMinimumWidth()。

接下來我們繼續看getSuggestedMinimumWidth()的源碼分析

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}

從代碼可以看出:

  • 若 View 無設置背景,那么View的寬度 = mMinWidth
  1. mMinWidth· = android:minWidth屬性所指定的值;
  2. 若android:minWidth沒指定,則默認為0
  • 若 View設置了背景,View的寬度為mMinWidth和mBackground.getMinimumWidth()中的最大值
public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    //返回背景圖Drawable的原始寬度
    return intrinsicWidth > 0 ? intrinsicWidth :0 ;
}

// 由源碼可知:mBackground.getMinimumWidth()的大小 = 背景圖Drawable的原始寬度
// 若無原始寬度,則為0;
// 注:BitmapDrawable有原始寬度,而ShapeDrawable沒有

總結:getDefaultSize()計算View的寬/高值的邏輯

image

至此,單一View的寬/高值已經測量完成,即對于單一View的measure過程已經完成。
總結
對于單一View的measure過程,如下:
image

實際作用的方法:getDefaultSize() = 計算View的寬/高值、setMeasuredDimension() = 存儲測量后的View寬 / 高

3.2 ViewGroup的measure過程

  • 應用場景
    利用現有的組件根據特定的布局方式來組成新的組件
  • 具體使用
    繼承自ViewGroup 或 各種Layout;含有子 View
  • 原理
  1. 遍歷 測量所有子View的尺寸
  2. 合并將所有子View的尺寸進行,最終得到ViewGroup父視圖的測量值
  • 流程


    image
  • 入口 = measure()

若需進行自定義ViewGroup,則需重寫onMeasure(),下文會提到

/**
  * 源碼分析:measure()
  * 作用:基本測量邏輯的判斷;調用onMeasure()
  * 注:與單一View measure過程中講的measure()一致
  **/ 
  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
            mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {

        // 調用onMeasure()計算視圖大小
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } else {
        ...
}
/**
  * 分析1:onMeasure()
  * 作用:遍歷子View & 測量
  * 注:ViewGroup = 一個抽象類 = 無重寫View的onMeasure(),需自身復寫
  **/
  • 因為不同的ViewGroup子類(LinearLayout、RelativeLayout / 自定義ViewGroup子類等)具備不同的布局特性,這導致他們子View的測量方法各有不同
  • 因此,ViewGroup無法對onMeasure()作統一實現。這個也是單一View的measure過程與ViewGroup過程最大的不同
  1. 即 單一View measure過程的onMeasure()具有統一實現,而ViewGroup則沒有
  2. 注:其實,在單一View measure過程中,getDefaultSize()只是簡單的測量了寬高值,在實際使用時有時需更精細的測量。所以有時候也需重寫onMeasure()
  • 在自定義ViewGroup中,關鍵在于:根據需求復寫onMeasure()從而實現你的子View測量邏輯。復寫onMeasure()的套路如下:
/**
  * 根據自身的測量邏輯復寫onMeasure(),分為3步
  * 1. 遍歷所有子View & 測量:measureChildren()
  * 2. 合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測量值(自身實現)
  * 3. 存儲測量后View寬/高的值:調用setMeasuredDimension()  
  **/ 

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

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

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

        // 2. 合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測量值
         void measureCarson{
             ... // 自身實現
         }

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

/**
  * 分析1:measureChildren()
  * 作用:遍歷子View & 調用measureChild()進行下一步測量
  **/ 

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

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

                // 遍歷所有子view
                for (int i = 0; i < size; ++i) {
                    final View child = children[i];
                     // 調用measureChild()進行下一步的測量 ->>分析1
                    if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                        measureChild(child, widthMeasureSpec, heightMeasureSpec);
                    }
                }
            }

/**
  * 分析2:measureChild()
  * 作用:a. 計算單個子View的MeasureSpec
  *      b. 測量每個子View最后的寬 / 高:調用子View的measure()
  **/ 
  protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {

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

        // 2. 根據父視圖的MeasureSpec & 布局參數LayoutParams,計算單個子View的MeasureSpec
        // getChildMeasureSpec() 請看上面第2節儲備知識處
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 獲取 ChildView 的 widthMeasureSpec
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 獲取 ChildView 的 heightMeasureSpec
                mPaddingTop + mPaddingBottom, lp.height);

        // 3. 將計算好的子View的MeasureSpec值傳入measure(),進行最后的測量
        // 下面的流程即類似單一View的過程,此處不作過多描述
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    // 回到調用原處

至此,ViewGroup的measure過程分析完畢


  • 總結
    ViewGroup的measure過程如下:


    image
  • 為了讓大家更好地理解ViewGroup的measure過程(特別是復寫onMeasure()),下面,我將用ViewGroup的子類LinearLayout來分析下ViewGroup的measure過程

3.3 ViewGroup的measure過程實例解析(LinearLayout)

此處直接進入LinearLayout復寫的onMeasure()代碼分析:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

      // 根據不同的布局屬性進行不同的計算
      // 此處只選垂直方向的測量過程,即measureVertical()->>分析1
      if (mOrientation == VERTICAL) {
          measureVertical(widthMeasureSpec, heightMeasureSpec);
      } else {
          measureHorizontal(widthMeasureSpec, heightMeasureSpec);
      }

      
}

/**
    * 分析1:measureVertical()
    * 作用:測量LinearLayout垂直方向的測量尺寸
    **/ 
  void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
      
      /**
       *  其余測量邏輯
       **/
          // 獲取垂直方向上的子View個數
          final int count = getVirtualChildCount();

          // 遍歷子View獲取其高度,并記錄下子View中最高的高度數值
          for (int i = 0; i < count; ++i) {
              final View child = getVirtualChildAt(i);

              // 子View不可見,直接跳過該View的measure過程,getChildrenSkipCount()返回值恒為0
              // 注:若view的可見屬性設置為VIEW.INVISIBLE,還是會計算該view大小
              if (child.getVisibility() == View.GONE) {
                 i += getChildrenSkipCount(child, i);
                 continue;
              }

              // 記錄子View是否有weight屬性設置,用于后面判斷是否需要二次measure
              totalWeight += lp.weight;

              if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                  // 如果LinearLayout的specMode為EXACTLY且子View設置了weight屬性,在這里會跳過子View的measure過程
                  // 同時標記skippedMeasure屬性為true,后面會根據該屬性決定是否進行第二次measure
                // 若LinearLayout的子View設置了weight,會進行兩次measure計算,比較耗時
                  // 這就是為什么LinearLayout的子View需要使用weight屬性時候,最好替換成RelativeLayout布局
                
                  final int totalLength = mTotalLength;
                  mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                  skippedMeasure = true;
              } else {
                  int oldHeight = Integer.MIN_VALUE;
     /**
       *  步驟1:遍歷所有子View & 測量:measureChildren()
       *  注:該方法內部,最終會調用measureChildren(),從而 遍歷所有子View & 測量
       **/
            measureChildBeforeLayout(

                   child, i, widthMeasureSpec, 0, heightMeasureSpec,
                   totalWeight == 0 ? mTotalLength : 0);
                   ...
            }

      /**
       *  步驟2:合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測量值(自身實現)
       **/        
              final int childHeight = child.getMeasuredHeight();

              // 1. mTotalLength用于存儲LinearLayout在豎直方向的高度
              final int totalLength = mTotalLength;

              // 2. 每測量一個子View的高度, mTotalLength就會增加
              mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                     lp.bottomMargin + getNextLocationOffset(child));
      
              // 3. 記錄LinearLayout占用的總高度
              // 即除了子View的高度,還有本身的padding屬性值
              mTotalLength += mPaddingTop + mPaddingBottom;
              int heightSize = mTotalLength;

      /**
       *  步驟3:存儲測量后View寬/高的值:調用setMeasuredDimension()  
       **/ 
       setMeasureDimension(resolveSizeAndState(maxWidth,width))

    ...

  }

至此,自定義View的中最重要、最復雜的measure過程講解完畢。

參考

自定義View Measure過程 - 最易懂的自定義View原理系列(2)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容