自定義View系列教程02--onMeasure源碼詳盡分析

引言

本章內(nèi)容較多,先養(yǎng)養(yǎng)眼


所侵權(quán),刪

大家知道,自定義View有三個(gè)重要的步驟:measure,layout,draw。而measure處于該鏈條的首端,占據(jù)著極其重要的地位;然而對(duì)于measure的理解卻不是那么容易,許多問題都是一知半解,比如:

  • 為什么父View影響到了子View的MeasureSpec的生成?
  • 為什么我們自定義一個(gè)View在布局時(shí)將其寬或者高指定為wrap_content但是其實(shí)際是match_parent的效果?
  • 子View的specMode和specSize的生成依據(jù)又是什么?

這些問題以前一直困擾著我,我就去找資料看,搜了一大筐,沮喪的發(fā)現(xiàn)這些文章大同小異:只舉個(gè)簡(jiǎn)單的例子,很少研究為什么;人云亦云,文章里的內(nèi)容沒有去驗(yàn)證和深究就發(fā)出來了;或者避重就輕直接把難點(diǎn)給繞過去了…….每次,看完這些文章就沒有勇氣去看layout和draw了,就算了;這可能就是《自定義View——從入門到放棄》的劇本吧??戳四敲炊辔恼乱琅f不能解答原來的疑惑;就像聽過了許多大道理依舊不過好這一生。連measure都沒有很好的理解又何談?wù)嬲睦斫鈒ayout和draw呢?要是能找到一篇文章能解開這些疑惑該有多好呀! 咦,等等。要是一直沒有找到這么一篇文章那又怎么辦呢?就真的不學(xué)習(xí)了?妙齡少婦郭大嬸不是說過么:每當(dāng)你在感嘆,如果有這樣一個(gè)東西就好了的時(shí)候,請(qǐng)注意,其實(shí)這是你的機(jī)會(huì)。是啊,機(jī)會(huì)來了。嗯,我們來一起搞清楚這些問題,我們從源碼里來尋找答案。

MeasureSpec基礎(chǔ)知識(shí)
系統(tǒng)顯示一個(gè)View,首先需要通過測(cè)量(measure)該View來知曉其長和寬從而確定顯示該View時(shí)需要多大的空間。在測(cè)量的過程中MeasureSpec貫穿全程,發(fā)揮著不可或缺的作用。 所以,了解View的測(cè)量過程,最合適的切入點(diǎn)就是MeasureSpec。 我們先來瞅瞅官方文檔對(duì)于MeasureSpec 的介紹:

A MeasureSpec encapsulates the layout requirements passed from parent to child.Each MeasureSpec represents a requirement for either the width or the height.A MeasureSpec is comprised of a size and a mode.

請(qǐng)注意這段話所包含的重要信息點(diǎn):

  1. MeasureSpec封裝了父布局傳遞給子View的布局要求。
  2. MeasureSpec可以表示寬和高
  3. MeasureSpec由size和mode組成

MeasureSpec通常翻譯為”測(cè)量規(guī)格”,它是一個(gè)32位的int數(shù)據(jù). 其中高2位代表SpecMode即某種測(cè)量模式,低30位為SpecSize代表在該模式下的規(guī)格大小. 可以通過如下方式分別獲取這兩個(gè)值:

//獲取specMode
int specMode = MeasureSpec.getMode(measureSpec)
//獲取SpecSize
int specSize = MeasureSpec.getSize(measureSpec)

當(dāng)然,也可以通過這兩個(gè)值生成新的MeasureSpec

int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

SpecMode一共有三種: MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , MeasureSpec.UNSPECIFIED
嗯哼,它們已經(jīng)躺在這里了,我們來挨個(gè)瞅瞅,每個(gè)SpecMode是什么意思
MeasureSpec.EXACTLY
官方文檔的描述:

The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.

MeasureSpec.EXACTLY模式表示:父容器已經(jīng)檢測(cè)出子View所需要的精確大小。 在該模式下,View的測(cè)量大小即為SpecSize。
MeasureSpec.AT_MOST
官方文檔的描述:

The child can be as large as it wants up to the specified size.

MeasureSpec.AT_MOST模式表示:父容器未能檢測(cè)出子View所需要的精確大小,但是指定了一個(gè)可用大小即specSize 在該模式下,View的測(cè)量大小不能超過SpecSize。
MeasureSpec.UNSPECIFIED
官方文檔的描述:

The parent has not imposed any constraint on the child. It can be whatever size it wants.

父容器不對(duì)子View的大小做限制.
MeasureSpec.UNSPECIFIED這種模式一般用作Android系統(tǒng)內(nèi)部,或者ListView和ScrollView等滑動(dòng)控件,在此不做討論。
看完了這三個(gè)SpecMode的含義,我們?cè)購脑创a里看看它們是怎么形成的。
在ViewGroup中測(cè)量子View時(shí)會(huì)調(diào)用到measureChildWithMargins()方法,或者與之類似的方法。源碼如下:

/**
     * @param child
     * 子View
     * @param parentWidthMeasureSpec
     * 父容器(比如LinearLayout)的寬的MeasureSpec
     * @param widthUsed
     * 父容器(比如LinearLayout)在水平方向已經(jīng)占用的空間大小
     * @param parentHeightMeasureSpec
     * 父容器(比如LinearLayout)的高的MeasureSpec
     * @param heightUsed
     * 父容器(比如LinearLayout)在垂直方向已經(jīng)占用的空間大小
     */
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
15      final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
16      final int childWidthMeasureSpec =
17                getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
18                                    lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
19        final int childHeightMeasureSpec =
20                  getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
21                                     lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
22        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

請(qǐng)務(wù)必注意該方法的參數(shù);明白這幾個(gè)參數(shù)的含義才能準(zhǔn)確理解方法的實(shí)現(xiàn)。
通過這些參數(shù)看出來一些端倪,該方法要測(cè)量子View傳進(jìn)來的參數(shù)卻包含了父容器的寬的MeasureSpec,父容器在水平方向已經(jīng)占用的空間大小,父容器的高的MeasureSpec,父容器在垂直方向已經(jīng)占用的空間大小等父View相關(guān)的信息。這在一定程度體現(xiàn)了:父View影響著子View的MeasureSpec的生成。

該方法主要有四步操作:
第一步: 得到子View的LayoutParams,請(qǐng)參見第15行代碼。
第二步: 得到子View的寬的MeasureSpec,請(qǐng)參見第16-18行代碼。
第三步: 得到子View的高的MeasureSpec,請(qǐng)參見第19-21行代碼。
第四步: 測(cè)量子View,請(qǐng)參見第22行代碼。

第一步,沒啥好說的;第二步和第三步都調(diào)用到了getChildMeasureSpec( ),在該方法內(nèi)部又做了哪些操作呢?

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
2       int specMode = View.MeasureSpec.getMode(spec);
3       int specSize = View.MeasureSpec.getSize(spec);

5        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

10      switch (specMode) {
11          case View.MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
21              }
22              break;

            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;

            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
47              }
48              break;
49      }
50      return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
51  }

請(qǐng)注意該方法的參數(shù):

spec
父容器(比如LinearLayout)的寬或高的MeasureSpec

padding
父容器(比如LinearLayout)在垂直方向或者水平方向已被占用的空間。
為什么這么說,它的依據(jù)在哪里?
請(qǐng)看在measureChildWithMargins()方法里調(diào)用getChildMeasureSpec()的地方,傳遞給getChildMeasureSpec()的第二個(gè)參數(shù)是如下構(gòu)成:
比如:mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin +widthUsed
其中:
mPaddingLeft和mPaddingRight表示父容器左右兩內(nèi)側(cè)的padding
lp.leftMargin和lp.rightMargin表示子View左右兩外側(cè)的margin
widthUsed水平方向已經(jīng)使用的空間大?。ㄈ缱覸iew在水平方向的占用空間)

這四部分都不可以再利用起來布局子View。所以說這些值的和表示:父容器在水平方向已經(jīng)被占用的空間
同理:mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +heightUsed,
表示: 父容器(比如LinearLayout)在垂直方向已被占用的空間.

childDimension
通過子View的LayoutParams獲取到的子View的寬或高

所以,從getChildMeasureSpec()方法的第一個(gè)參數(shù)spec和第二個(gè)參數(shù)padding也可以看出:
父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同決定了子View的MeasureSpec!

明白了該方法的參數(shù),我們?cè)賮砜捶椒ǖ木唧w實(shí)現(xiàn)步驟。

  • 第一步:得到父容器的specMode和specSize,請(qǐng)參見第2-3行代碼。
  • 第二步: 得到父容器在水平方向或垂直方向可用的最大空間值,請(qǐng)參見第5行代碼。
  • 第三步: 確定子View的specMode和specSize,請(qǐng)參見第10-50行代碼。

在上面的代碼塊中出現(xiàn)了一個(gè)很關(guān)鍵的switch語句,該語句的判斷條件就是父View的specMode;在此根據(jù)父View的specMode的不同來決定子View的specMode和specSize.

情況1:

父容器的specMode為MeasureSpec.EXACTLY,請(qǐng)參見第11-22行代碼。
也請(qǐng)記住該先決條件,因?yàn)橐韵碌挠懻摱际腔诖苏归_的。

1、我們首先看到一個(gè)if判斷
if (childDimension >= 0),或許看到這有點(diǎn)懵了:childDimension>=0是啥意思?難道還有小于0的情況?是的,請(qǐng)注意兩個(gè)系統(tǒng)常量:
LayoutParams.MATCH_PARENT = -1 和 LayoutParams.WRAP_CONTENT = -2

所以在此處的代碼:
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個(gè)具體的數(shù)值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、看完這個(gè)if,我們來看第一個(gè)else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = size; 
resultMode = MeasureSpec.EXACTLY;

3、我們來看第二個(gè)else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode為MeasureSpec.AT_MOST,即:

resultSize = size; 
resultMode = MeasureSpec.AT_MOST;

情況2:

父容器的specMode為MeasureSpec.AT_MOST,請(qǐng)參見第24-35行代碼。
也請(qǐng)記住該先決條件,因?yàn)橐韵碌挠懻摱际腔诖苏归_的。

1、還是先看這個(gè)if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個(gè)具體的數(shù)值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、繼續(xù)看第一個(gè)else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST,即:

resultSize = size; 
resultMode = MeasureSpec.AT_MOST;

3、接著看第二個(gè)else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size就是父容器在水平方向或垂直方向可用的最大空間值即size,子View的mode也為MeasureSpec.AT_MOST

resultSize = size;
resultMode = MeasureSpec.AT_MOST;

情況3:

父容器的specMode為MeasureSpec.UNSPECIFIED,請(qǐng)參見第37-48行代碼。
也請(qǐng)記住該先決條件,因?yàn)橐韵碌挠懻摱际腔诖苏归_的。

1、還是先看這個(gè)if判斷
if (childDimension >= 0)
表示子View的寬或高不是match_parent,也不是wrap_content而是一個(gè)具體的數(shù)值,比如100px。
那么:子View的size就是childDimension,子View的mode也為MeasureSpec.EXACTLY,即:

resultSize = childDimension; 
resultMode = MeasureSpec.EXACTLY;

2、繼續(xù)看第一個(gè)else if
else if (childDimension == LayoutParams.MATCH_PARENT)
表示子View的寬或高是LayoutParams.MATCH_PARENT。
那么:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:

resultSize = 0; 
resultMode = MeasureSpec.UNSPECIFIED;

3、接著看第二個(gè)else if
else if (childDimension == LayoutParams.WRAP_CONTENT)
表示子View的寬或高是LayoutParams.WRAP_CONTENT。
那么:子View的size為0,子View的mode為MeasureSpec.UNSPECIFIED,即:

resultSize = 0; 
resultMode = MeasureSpec.UNSPECIFIED;

至此,我們可以清楚地看到:
子View的MeasureSpec由其父容器的MeasureSpec和該子View本身的布局參數(shù)LayoutParams共同決定。
在此經(jīng)過測(cè)量得出的子View的MeasureSpec是系統(tǒng)給出的一個(gè)期望值(參考值),我們也可摒棄系統(tǒng)的這個(gè)測(cè)量流程,直接調(diào)用setMeasuredDimension( )設(shè)置子View的寬和高的測(cè)量值。

對(duì)于以上的分析可用表格來規(guī)整各一下MeasureSpec的生成


View的MeasureSpace創(chuàng)建規(guī)則

好了,看到這個(gè)圖,感覺清晰多了。為了便于理解和記憶,我在此再用大白話再對(duì)該圖進(jìn)行詳細(xì)的描述:

在哪些具體的情況下子View的SpecMode為MeasureSpec.EXACTLY? 在此,對(duì)各情況一一討論和分析:

第一種情況:

當(dāng)子View的LayoutParams的寬(高)采用具體的值(如100px)時(shí)且父容器的MeasureSpec為MeasureSpec.EXACTLY或者M(jìn)easureSpec.AT_MOST或者M(jìn)easureSpec.UNSPECIFIED時(shí):系統(tǒng)返回給該子View的specMode就為MeasureSpec.EXACTLY,系統(tǒng)返回給該子View的specSize就為子View自己指定的大小(childSize)。

通俗地理解: 子View的LayoutParams的寬(高)采用具體的值(如100px)時(shí),那么說明該子View的大小是非常明確的,明確到了令人發(fā)指的地址,都已經(jīng)到了用具體px值指定的地步了。那么此時(shí)不管父容器的specMode是什么,系統(tǒng)返回給該子View的specMode總是MeasureSpec.EXACTLY,并且系統(tǒng)返回給該子View的specSize就是子View自己指定的大小(childSize)。

第二種情況:

當(dāng)子View的LayoutParams的寬(高)采用match_parent時(shí)并且父容器的MeasureSpec為MeasureSpec.EXACTLY時(shí):系統(tǒng)返回給該子View的specMode就為 MeasureSpec.EXACTLY,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)。

通俗地理解: 子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.EXACTLY。這時(shí)候說明子View的大小還是挺明確的:就是要和父容器一樣大,更加直白地說就是父容器要怎樣子View就要怎樣。所以,如果父容器MeasureSpec為MeasureSpec.EXACTLY,那么系統(tǒng)返回給該子View的specMode就為 MeasureSpec.EXACTLY和父容器一樣;系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.

在哪些具體的情況下子View的SpecMode為MeasureSpec.AT_MOST? 在此,對(duì)各情況一一討論和分析:

第一種情況:

當(dāng)子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.AT_MOST時(shí):系統(tǒng)返回給該子View的specMode就為MeasureSpec.AT_MOST,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

通俗地理解: 子View的LayoutParams的寬(高)采用match_parent并且父容器的MeasureSpec為MeasureSpec.AT_MOST。這時(shí)候說明子View的大小還是挺明確的:就是要和父容器一樣大,直白地說就是父容器要怎樣子View就要怎樣。但是此時(shí)父容器的大小不是很明確其MeasureSpec為MeasureSpec.AT_MOST,那么系統(tǒng)返回給該子View的specMode就為MeasureSpec.AT_MOST和父容器一樣;系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize),即為父容器的剩余大小.

第二種情況:

當(dāng)子View的LayoutParams的寬(高)采用wrap_content時(shí)并且父容器的MeasureSpec為MeasureSpec.EXACTLY時(shí):系統(tǒng)返回給該子View的specMode就為 MeasureSpec.AT_MOST,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

通俗地理解: 子View的LayoutParams的寬(高)采用wrap_content時(shí)說明這個(gè)子View的寬高不明確,要視content而定。這時(shí)如果父容器的MeasureSpec為MeasureSpec.EXACTLY即父容器是一個(gè)精確模式。這種情況概況起來簡(jiǎn)單地說就是:子View大小是不確定的,但父容器大小是確定的,那么系統(tǒng)返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

第三種情況:

當(dāng)子View的LayoutParams的寬(高)采用wrap_content時(shí)并且父容器的MeasureSpec為MeasureSpec.AT_MOST時(shí):系統(tǒng)返回給該子View的specMode就為MeasureSpec.AT_MOST,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)
通俗地理解: 子View的LayoutParams的寬(高)采用wrap_content,即說明這個(gè)子View的寬高不明確,要視content而定。這個(gè)時(shí)候父容器的MeasureSpec為MeasureSpec.AT_MOST。這種情況概況起來簡(jiǎn)單地說就是:子View的寬高是不確定的,父容器的寬高也是不確定的,那么系統(tǒng)返回給該子View的specMode也就是不確定的即為MeasureSpec.AT_MOST,系統(tǒng)返回給該子View的specSize就為該父容器剩余空間的大小(parentLeftSize)

在哪些具體的情況下子View的SpecMode為MeasureSpec.UNSPECIFIED?

前面也說了該模式在實(shí)際開發(fā)中極少用到,故在此不做討論。

剛才我們討論的是: measureChildWithMargins( )調(diào)用getChildMeasureSpec( )
除此以外還有一種常見的操作: measureChild( )調(diào)用getChildMeasureSpec( )
那么,measureChildWithMargins( )和measureChild( )有什么相同點(diǎn)和區(qū)別呢?

  • measureChildWithMargins( )和measureChild( )均用來測(cè)量子View的大小
  • 兩者在調(diào)用getChildMeasureSpec( )均要計(jì)算父View已占空間
  • 在measureChild( )計(jì)算父View所占空間為mPaddingLeft + mPaddingRight,即父容器左右兩側(cè)的padding值之和
  • measureChildWithMargins( )計(jì)算父View所占空間為mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed。此處,除了父容器左右兩側(cè)的padding值之和還包括了子View左右的margin值之和( lp.leftMargin + lp.rightMargin),因?yàn)檫@兩部分也是不能用來擺放子View的應(yīng)算作父View已經(jīng)占用的空間。這點(diǎn)從方法名measureChildWithMargins也可以看出來它是考慮了子View的margin所占空間的。

其實(shí),關(guān)于measureChildWithMargins()在源碼注釋中也說了:

Ask one of the children of this view to measure itself, taking into account both the MeasureSpec requirements for this view and its padding and margins.

好了,搞懂了MeasureSpec我們才正真地進(jìn)入到View的onMeasure()源碼分析。

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

onMeasure( )源碼流程如下:
(1) 在onMeasure調(diào)用setMeasuredDimension( )設(shè)置View的寬和高.
(2) 在setMeasuredDimension()中調(diào)用getDefaultSize()獲取View的寬和高.
(3) 在getDefaultSize()方法中又會(huì)調(diào)用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()獲取到View寬和高的最小值.
即這一系列的方法調(diào)用順序?yàn)?


onMeasure()方法中調(diào)用順序

為了理清這幾個(gè)方法間的調(diào)用及其作用,在此按照倒序分析每個(gè)方法的源碼。
先來看getSuggestedMinimumWidth( )

//Returns the suggested minimum width that the view should use 
protected int getSuggestedMinimumWidth() {  
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());  
} 

該方法返回View的寬度的最小值MinimumWidth.
在此需要注意該View是否有背景.
(1) 若該View沒有背景。 那么該MinimumWidth為View本身的最小寬度即mMinWidth。 有兩種方法可以設(shè)置該mMinWidth值: 第一種:XML布局文件中定義minWidth ;第二種:調(diào)用View的setMinimumWidth()方法為該值賦值;

(2) 若該View有背景。 那么該MinimumWidth為View本身最小寬度mMinWidth和View背景的最小寬度的最大值

getSuggestedMinimumHeight()方法與此處分析很類似,故不再贅述.
接下來看看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;  
     }  

該方法用于獲取View的寬或者高的大小。
該方法的第一個(gè)輸入?yún)?shù)size就是調(diào)用getSuggestedMinimumWidth()方法獲得的View的寬或高的最小值。

從getDefaultSize()的源碼里的switch可看出該方法的返回值有兩種情況:
(1) measureSpec的specMode為MeasureSpec.UNSPECIFIED
在此情況下該方法的返回值就是View的寬或者高最小值.
該情況很少見,基本上可忽略

(2) measureSpec的specMode為MeasureSpec.AT_MOST或MeasureSpec.EXACTLY:
在此情況下getDefaultSize()的返回值就是該子View的measureSpec中的specSize。

除去第一種情況不考慮以外,可知:
在measure階段View的寬和高由其measureSpec中的specSize決定??!

看了這么久的源碼,我們終于搞清楚了這個(gè)問題;但是剛剛舒展開的眉頭又皺起來了。結(jié)合剛才的圖發(fā)現(xiàn)一個(gè)問題:在該圖的最后一行,如果子View在XML布局文件中對(duì)于大小的設(shè)置采用wrap_content,那么不管父View的specMode是MeasureSpec.AT_MOST還是MeasureSpec.EXACTLY對(duì)于子View而言系統(tǒng)給它設(shè)置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空間。這時(shí)wrap_content就失去了原本的意義,變成了match_parent一樣了.

所以自定義View在重寫onMeasure()的過程中應(yīng)該手動(dòng)處理View的寬或高為wrap_content的情況。

至此,已經(jīng)看完了getSuggestedMinimumWidth()和getDefaultSize()
最后再來看setMeasuredDimension( )的源碼

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
     mMeasuredWidth = measuredWidth;  
     mMeasuredHeight = measuredHeight;  
     mPrivateFlags |= MEASURED_DIMENSION_SET;  
}

經(jīng)過了前面的一系列操作,終于得到了View的寬高。
在此調(diào)用setMeasuredDimension( )設(shè)置View的寬和高的測(cè)量值


好了,關(guān)于View的onMeasure( )的源碼及其調(diào)用流程都已經(jīng)分析完了。 但是,剛才還遺留了一個(gè)問題: 自定義View在重寫onMeasure()的過程中要處理View的寬或高為wrap_content的情況(請(qǐng)參見下圖中的綠色標(biāo)記部分)
View的MeasureSpec創(chuàng)建規(guī)則

我們?cè)撛趺刺幚砟兀?br> 第一種情況:
如果在xml布局中View的寬和高均用wrap_content.那么需要設(shè)置
View的寬和高為mWidth和mHeight.
第二種情況:
如果在xml布局中View的寬或高其中一個(gè)為wrap_content,那么就將該值設(shè)置為默認(rèn)的寬或高,另外的一個(gè)值采用系統(tǒng)測(cè)量的specSize即可.

具體的實(shí)現(xiàn)可以這樣做:

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

4   int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);  
5   int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);  

    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);  
    int heightSpceSize = MeasureSpec.getSize(heightMeasureSpec);  

    if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){  
        setMeasuredDimension(mWidth, mHeight);  
    }else if(widthSpecMode==MeasureSpec.AT_MOST){  
        setMeasuredDimension(mWidth, heightSpceSize);  
    }else if(heightSpecMode==MeasureSpec.AT_MOST){  
15      setMeasuredDimension(widthSpceSize, mHeight);  
16  }  

 }  

該部分的處理主要有兩步

第一步:
調(diào)用super.onMeasure(),請(qǐng)參見第2行代碼

第二步:
處理子View的大小為wrap_content的情況,請(qǐng)參見第4-16行代碼。
此處涉及到的mWidth和mHeight均為一個(gè)默認(rèn)值;應(yīng)根據(jù)具體情況而設(shè)值。
其實(shí),Andorid系統(tǒng)的控件比如TextView等也在onMeasure()中對(duì)其大小為wrap_content這一情況作了特殊的處理。

請(qǐng)注意在第二步的代碼中用的判斷條件:
widthSpecMode==MeasureSpec.AT_MOST或者h(yuǎn)eightSpecMode==MeasureSpec.AT_MOST或者兼而有之;總之,這里的著眼點(diǎn)是模式MeasureSpec.AT_MOST。
看到這里再聯(lián)想到下圖就有一個(gè)疑問了(請(qǐng)參見下圖中的紅色標(biāo)記部分):


View的MeasureSpec創(chuàng)建規(guī)則

如果子View在布局文件中采用match_parent,并且父容器的SpecMode為MeasureSpec.AT_MOST, 那么此時(shí)該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize。

既然此時(shí)該子View的SpecMode也為MeasureSpec.AT_MOST那么當(dāng)執(zhí)行到onMeasure()時(shí)按照我們的判斷邏輯,它的寬或者高至少有一個(gè)會(huì)被設(shè)置成默認(rèn)值(mWidth和mHeight)。說白了,本來是個(gè)match_parent,卻被設(shè)置成了具體指的默認(rèn)值。

看到這里好像覺得也沒啥不對(duì),但是不符合常理!!!到底是哪里錯(cuò)了??? 我們這么想: 在什么情況下父容器的SpecMode為MeasureSpec.AT_MOST? 這個(gè)問題不難回答,共有兩種情況:
情況1: 當(dāng)父容器的大小為wrap_content時(shí)系統(tǒng)給父容器的SpecMode為MeasureSpec.AT_MOST.
情況2: 當(dāng)父容器的大小為match_parent時(shí)系統(tǒng)給父容器的SpecMode為MeasureSpec.AT_MOST.

回答了這個(gè)問題就以此答案為基礎(chǔ)繼續(xù)討論。 剛才的問題就可以描述為以下兩種情況:
情況1: 當(dāng)父容器大小為wrap_content且其specMode為MeasureSpec.AT_MOST,子View大小為match_parent。 也就是說:子View想和父容器一樣大但父容器的大小又設(shè)定為包裹內(nèi)容大小即wrap_content。那么,到底誰決定誰呢?誰也不能決定誰!父View和子View這父子倆就這么耗上了。 所以,該情況是理論上存在的但在實(shí)際情況中是很不合理甚至錯(cuò)誤的,當(dāng)然也是不可取的。

情況2: 當(dāng)父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,子View大小為match_parent。
既然父容器大小為match_parent且其SpecMode為MeasureSpec.AT_MOST,那么父容器的父容器(以下簡(jiǎn)稱“爺容器”)又該是什么情況呢?

  1. 爺容器的大小不可能是wrap_content(原理同情況1)
  2. 爺容器的大小不可能是某個(gè)具體的值。 因?yàn)槿羝浯笮槟尘唧w值,那么其specMode應(yīng)該為MeasureSpec.EXACTLY;父容器的specMode也該為MeasureSpec.EXACTLY。但是這里父容器的SpecMode為MeasureSpec.AT_MOST,相互矛盾了。
  3. 爺容器的大小是match_parent;那么其SpecMode有兩種情況:MeasureSpec.EXACTLY或者M(jìn)easureSpec.AT_MOST。 在此,為了便于理清思路,繼續(xù)分情況來討論 第一種情況: 爺容器的大小是match_parent,SpecMode為MeasureSpec.EXACTLY,且父容器此時(shí)大小為match_parent;那么父容器的SpecMode應(yīng)該為MeasureSpec.EXACTLY;但是這里父容器的SpecMode為MeasureSpec.AT_MOST。兩者是矛盾的。所以不會(huì)出現(xiàn)這個(gè)情況。 第二種情況: 爺容器的大小是match_parent,SpecMode為MeasureSpec.AT_MOST,且父容器此時(shí)大小為match_parent,那么父容器的SpecMode可以為MeasureSpec.AT_MOST。這是唯一可能存在的情況。 試著將這種情況抽取出來,就陷入到一個(gè)循環(huán):子View大小是match_parent其SpecMode為MeasureSpec.AT_MOST;父View的大小也是match_parent其SpecMode也為MeasureSpec.AT_MOST,爺容器亦如此……..直到根View根為止。但是根View的大小如果為match_parent,那么其SpecMode必為MeasureSpec.EXACTLY。所以這種情況也是矛盾的,也不會(huì)出現(xiàn)。
    至此,綜上所述,我們發(fā)現(xiàn):子View在布局文件中采用match_parent,并且父容器的SpecMode為MeasureSpec.AT_MOST,那么此時(shí)該子View的SpecMode也為MeasureSpec.AT_MOST且其View的大小為parentLeftSize這種情況本身(圖中紅色標(biāo)記部分)就是不合理的,不可取的。將這個(gè)問題再次抽取就可以簡(jiǎn)化為情況1,殊途同歸。
    以此類推:
    (1) 不可能出現(xiàn)根View的大小為wrap_content但它的一個(gè)子View大小為match_parent。
    (2) 從根到這個(gè)子View的父容器都是wrap_content,而子View的大小為match_parent。這個(gè)極端情況也是不會(huì)的,可見情況1的分析.
    (3)從根到這個(gè)子View的父容器都是wrap_content,而子View大小也為wrap_content。這是個(gè)正常情況也正是我們改良后的onMeasure()來專門處理的子View大小為wrap_content的情況。

剛分析完了View的onMeasure()源碼,現(xiàn)在接著看ViewGroup的measure階段的實(shí)現(xiàn)

public abstract class ViewGroup extends View implements ViewParent,ViewManager{ }

首先,請(qǐng)注意ViewGroup是一個(gè)抽象類,它沒有重寫View的onMeasure( )但它提供了measureChildren( )測(cè)量所有的子View。在measureChildren()方法中調(diào)用measureChild( )測(cè)量每個(gè)子View,在該方法內(nèi)又會(huì)調(diào)用到child.measure( )方法,這又回到了前面熟悉的流程。 即ViewGroup中測(cè)量子View的流程: measureChildren( )—>measureChild( )—>child.measure( )
既然ViewGroup繼承自View而且它是一個(gè)抽象類,那么ViewGroup的子類就應(yīng)該根據(jù)自身的要求和特點(diǎn)重寫onMeasure( )方法。 那么這些ViewGroup的子類會(huì)怎么測(cè)量子View和確定自身的大小呢? 假若ViewGroup知道了每個(gè)子View的大小,將它們累加起來是不是就知道了自身的大小呢?
順著這個(gè)猜想,我們選擇ViewGroup的子類LinearLayout瞅瞅,就從它的onMeasure( )開始看吧

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

嗯哼,代碼里分了水平線性和垂直線性這兩種情況進(jìn)行測(cè)量,類似的邏輯在其onLayout( )階段也會(huì)看到的。我們就選measureVertical( )繼續(xù)往下看

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

        final int count = getVirtualChildCount();

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;        
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                if (lp.height == 0 && lp.weight > 0) {

                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }


                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);

                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }

                final int childHeight = child.getMeasuredHeight();
               final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }


            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("Exception");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

115         for (int i = 0; i < count; ++i) {
116             final View child = getVirtualChildAt(i);
117
118             if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
130             final int totalLength = mTotalLength;
131             mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
132                     lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
133         }
        }

        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);

                if (child.getVisibility() == View.GONE) {
                    continue;
                }

                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

                float childExtra = lp.weight;
                if (childExtra > 0) {
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);


                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }

                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }

                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            mTotalLength += mPaddingTop + mPaddingBottom;
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);

            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);

                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

238     maxWidth += mPaddingLeft + mPaddingRight;
239     maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
240
241     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

這段代碼的主要操作:

遍歷每個(gè)子View,并對(duì)每個(gè)子View調(diào)用measureChildBeforeLayout(),請(qǐng)參見代碼第115-133行
在measureChildBeforeLayout()方法內(nèi)又會(huì)調(diào)用measureChildWithMargins()從而測(cè)量每個(gè)子View的大小。在該過程中mTotalLength保存了LinearLayout的高度,所以每當(dāng)測(cè)量完一個(gè)子View該值都會(huì)發(fā)生變化。
設(shè)置LinearLayout的大小,請(qǐng)參見代碼第241
以上就是LinearLayout的onMeasure( )的簡(jiǎn)要分析。
其余的ViewGroup的子類均有自己各不相同的measure操作,具體實(shí)現(xiàn)請(qǐng)參考對(duì)應(yīng)的源碼。

終于,分析完了和Measure有關(guān)的主要代碼。
代碼稍微有點(diǎn)多,所以就有點(diǎn)小小的犯暈了。
再回過頭看剛才討論過的兩個(gè)方法:

以下為measureChildWithMargins

/**
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     * @param parentHeightMeasureSpec The height requirements for this view
     * @param heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {

    }

對(duì)于參數(shù)parentWidthMeasureSpec和parentHeightMeasureSpec注釋的描述是:

The width(height) requirements for this view
父容器對(duì)子View寬(高)的要求(或者說是期望值)。

以下為getChildMeasureSpec

/**
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and margins, if applicable
     * @param childDimension How big the child wants to be in the current dimension
     * @return a MeasureSpec integer for the child
     */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

    }

注釋對(duì)于第一個(gè)參數(shù)spec的描述也是類似的:

The requirements for this view
咦,為什么我們?cè)诮庾x這兩個(gè)方法的時(shí)候說:
parentWidthMeasureSpec和parentHeightMeasureSpec和spec這三個(gè)值表示寬或者高的MeasureSpec呢?

我們的表述和注釋的說明怎么不一樣呢?
難道我們理解錯(cuò)了?非也,非也。

子View的MeasureSpec是由父View的MeasureSpec及子View自身的LayoutParam共同決定的。
關(guān)于“子View自身的LayoutParam”好理解,就是子View的寬或高等條件
那“父View的MeasureSpec”又體現(xiàn)在哪里呢?
當(dāng)然就是這里出現(xiàn)的parentWidthMeasureSpec和parentHeightMeasureSpec以及spec。只不過文檔說得委婉些“對(duì)于子View的要求(期望)”。
其實(shí),我個(gè)人覺得理解成為“要求”更好一些,就是父View對(duì)子View的要求。比如,在絕大多數(shù)情況下父View要求子View的大小不能超過其大小,這就是一種要求。而父View的大小就封裝在父View的MeasureSpec里的。
所以,可以從這個(gè)角度來體會(huì)文檔的描述。
嗯哼,終于看完了measure的過程。
當(dāng)理解了MeasureSpec和measure的原理,我們才能更好的理解layout和draw從而掌握自定義View的流程。


who is the next one? ——> layout

原文地址:http://blog.csdn.net/lfdfhl/article/details/51347818

自定義View系列教程01--常用工具介紹
自定義View系列教程02--onMeasure源碼詳盡分析
自定義View系列教程03--onLayout源碼詳盡分析
自定義View系列教程04--Draw源碼分析及其實(shí)踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發(fā)Touch事件
自定義View系列教程08–滑動(dòng)沖突的產(chǎn)生及其處理

參考文章
Activity中setContentView剖析
Android走進(jìn)Framework之AppCompatActivity.setContentView

Android LayoutInflater原理分析,帶你一步步深入了解View(一)
Android視圖繪制流程完全解析,帶你一步步深入了解View(二)
Android視圖狀態(tài)及重繪流程分析,帶你一步步深入了解View(三)
Android自定義View的實(shí)現(xiàn)方法,帶你一步步深入了解View(四)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容