自定義View原理篇(1)- measure過程

1. 簡介

  • View的繪制過程分為三部分:measure逃默、layout呢堰、draw

measure用來測量View的寬和高卵凑。
layout用來計算View的位置庆聘。
draw用來繪制View。

  • 本章主要對measure過程進行詳細的分析勺卢。
  • 本文源碼基于android 27

2. measure的始點

measure是從ViewRootImplperformTraversals()方法開始的:

2.1 ViewRootImpl的performTraversals

    private void performTraversals() {

        //...

        //獲得view寬高的測量規(guī)格象对,mWidth和mHeight表示窗口的寬高黑忱,lp.widthhe和lp.height表示DecorView根布局寬和高
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量

        //...
    }

首先會獲取view寬高的測量規(guī)格,測量規(guī)格在下一節(jié)會詳細講述勒魔,然后就是調(diào)用performMeasure()了:

2.2 ViewRootImpl的performMeasure

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

        //...

        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

        //...
    }

performMeasure()中就是調(diào)用Viewmeasure()方法開始進行測量甫煞。

3.MeasureSpec

了解measure的過程時,我們須先了解MeasureSpec冠绢。MeasureSpec抚吠,顧名思義,就是測量規(guī)格弟胀,其決定了一個View的寬和高楷力。

3.1 MeasureSpec組成

MeasureSpec代表一個32位的int值,前2位代表SpecMode孵户,后30位代表SpecSize萧朝。其中:SpecMode代表測量的模式,SpecSize值在某種測量模式下的規(guī)格大小夏哭。來張圖解說明:

image

3.2 SpecMode測量模式

測量模式分為三種UNSPECIFIED检柬、EXACTLYAT_MOST竖配,其具體說明如下表所示:

測量模式 說明 應(yīng)用場景
UNSPECIFIED (未指定) 父容器沒有對當(dāng)前View有任何限制何址,當(dāng)前View可以任意取尺寸 系統(tǒng)內(nèi)部
EXACTLY(精確) 父容器已經(jīng)確定當(dāng)前View的大小,無論View想要多大都會在這范圍內(nèi) match_parent 和 具體數(shù)值
AT_MOST(最多) 當(dāng)前View不能超過父容器規(guī)格大小进胯,具體數(shù)值由view去決定 wrap-content

3.3 MeasureSpec源碼分析

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;//模式移位數(shù)
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;//模式掩碼

        //UNSPECIFIED模式:父容器沒有對當(dāng)前View有任何限制用爪,當(dāng)前View可以任意取尺寸
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        //EXACTLY模式:父容器已經(jīng)確定當(dāng)前View的大小,無論View想要多大都會在這范圍內(nèi)
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        //AT_MOST模式:當(dāng)前View不能超過父容器規(guī)格大小龄减,具體數(shù)值由view去決定
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //根據(jù)提供的size和mode得到一個測量規(guī)格
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            //sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
            //即targetSdkVersion<=17時项钮,size與mode是直接相加的;>17則進行位運算
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);//位運算
            }
        }

        //根據(jù)提供的size和mode得到一個測量規(guī)格 
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        //獲取測量模式
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        //獲取測量大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

    }

可以看到,MeasureSpec類還是挺簡單的希停,MeasureSpec類通過將modesize打包成一個32位int值來減少了對象內(nèi)存分配烁巫,并提供了打包和解包的方法。

3.4 確定MeasureSpec值

measure過程中宠能,系統(tǒng)會將ViewLayoutParams和父容器所施加的規(guī)則轉(zhuǎn)換成對應(yīng)的MeasureSpec亚隙,然后在onMeasure()方法中根據(jù)這個MeasureSpec來確定View的測量寬高。父容器所施加的規(guī)則對于DecorView與普通View是不同的违崇,我們分開來看阿弃。

3.4.1 確定DecorView的MeasureSpec值

DecorView诊霹,作為頂級的View,我們平時setContentView()所設(shè)置的布局可能只是DecorView其中的一部分渣淳,如下圖所示:

image

關(guān)于DecorView脾还,可以看看我的另一篇文章:從setContentView揭開DecorView

3.4.1.1 ViewRootImpl的PerformTraveals

ViewRootImplPerformTraveals()方法中會獲得DecorViewMeasureSpec值:

    private void performTraversals() {

        //...

        //獲得view寬高的測量規(guī)格入愧,mWidth和mHeight表示窗口的寬高鄙漏,lp.widthhe和lp.height表示DecorView根布局寬和高
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量

        //...
    }

3.4.1.2 ViewRootImpl的getRootMeasureSpec

我們再來看看getRootMeasureSpec()方法:

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            //如果View的布局參數(shù)為MATCH_PARENT,則其為精確模式棺蛛,大小為窗口的大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            //如果View的布局參數(shù)為WRAP_CONTENT怔蚌,則其為最多模式,大小不定旁赊,但不能超過窗口的大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            //如果View的布局參數(shù)為準(zhǔn)確的數(shù)值桦踊,如100dp等,則其為精確模式终畅,大小為View設(shè)定的數(shù)值
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

可以看到:DecorView的MeasureSpec值是由窗口大小及其布局參數(shù)來決定的籍胯。

來張表格總結(jié):

布局參數(shù) LayoutParams.MATCH_PARENT LayoutParams.WRAP_CONTENT 準(zhǔn)確數(shù)值
MeasureSpec值 EXACTLY
windowSize(窗口大小) AT_MOST
windowSize(不能超過窗口大猩搿) EXACTLY
rootDimension(準(zhǔn)確數(shù)值)

3.4.2 確定普通View的MeasureSpec值

普通ViewMeasureSpec值通過getChildMeasureSpec()方法來獲取芒炼,getChildMeasureSpec()位于ViewGroup中:

3.4.2.1 ViewGroup的getChildMeasureSpec


    /**
     *
     * @param spec 父容器的測量規(guī)格(MeasureSpec)
     * @param padding 子view的內(nèi)邊距和外邊距(padding,margin) 
     * @param childDimension 子view的布局參數(shù)(寬/高)
     * @return 子view的測量規(guī)格(MeasureSpec)
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //父容器的測量模式
        int specMode = MeasureSpec.getMode(spec);
        //父容器的測量大小
        int specSize = MeasureSpec.getSize(spec);

        //子view大小 = 父容器大小-子view邊距(子view只在某些地方地方用到這個值,具體看下面的代碼)
        int size = Math.max(0, specSize - padding);

        //初始化子view的大小和模式(最終的結(jié)果需要下面代碼計算出來) 
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {

        case MeasureSpec.EXACTLY://當(dāng)父容器模式為EXACTLY時
            if (childDimension >= 0) {// 當(dāng)子view的LayoutParams>0术徊,即有確切的值
                //子view大小為子自身所賦的值本刽,模式大小為EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {// 當(dāng)子view的LayoutParams為MATCH_PARENT時(-1)
                //子view大小為父容器剩余大小,模式為EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 當(dāng)子view的LayoutParams為WRAP_CONTENT時(-2)
                //子view決定自己的大小赠涮,但最大不能超過父容器剩余大小子寓,模式為AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST: // 當(dāng)父容器的模式為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;

        case MeasureSpec.UNSPECIFIED:// 當(dāng)父容器的模式為UNSPECIFIED時
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //這里的sUseZeroUnspecifiedMeasureSpec值為targetSdkVersion < Build.VERSION_CODES.M;即<23為true,否則為false.
                //大于等于23時會將size作為提示值,否則為0
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }

        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

可以看到:普通View的MeasureSpec值是由父容器的MeasureSpec及其布局參數(shù)來決定的笋除。

來張表格總結(jié):

| 父容器測量模式(右側(cè))

子View布局參數(shù)(下側(cè)) EXACTLY(精確) AT_MOST(最多) UNSPECIFIED(未指定)
具體dp/px EXACTLY
childDimension(子View尺寸) EXACTLY
childDimension(子View尺寸) EXACTLY
childDimension(子View尺寸)
MATCH_PARENT EXACTLY
parentSize(父容器剩余大行庇选) AT_MOST
parentSize(不能超過父容器剩余大小) UNSPECIFIED

targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值) |
| WRAP_CONTENT | AT_MOST
parentSize(不能超過父容器剩余大欣) | AT_MOST
parentSize(不能超過父容器剩余大邢势痢)) | UNSPECIFIED
targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值) |

對上表作一些規(guī)律總結(jié):

  • 以子View布局參數(shù)為標(biāo)準(zhǔn),橫向觀察:
  1. 當(dāng)子View使用具體的dp/px時国拇,無論父容器的測量模式是什么洛史,子View的測量模式都是EXACTLY且大小等于設(shè)置的具體數(shù)值;
  2. 當(dāng)子View使用match_parent時酱吝,子View的測量模式與父容器的測量模式保存一致也殖。

另外,UNSPECIFIED模式一般很少用到务热,可以不用過多關(guān)注忆嗜。

4. measure過程分析

measure過程根據(jù)View的類型可以分為兩種情況:

  1. 測量單一View時己儒,只需測量自身;
  2. 測量ViewGroup時捆毫,需要對ViewGroup中包含的所有子View都進行測量闪湾。

我們對這兩種情況分別進行分析。

4.1 單一View的measure過程

單一Viewmeasure過程由Viewmeasure()來實現(xiàn):

4.1.1 View的measure

    /**
     *
     * @param widthMeasureSpec view的寬測量規(guī)格
     * @param heightMeasureSpec view的高測量規(guī)格
     */
   public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        //...
   }

這里傳入的widthMeasureSpec/heightMeasureSpec是當(dāng)前View的測量規(guī)格冻璃,通過getChildMeasureSpec()來獲得的响谓。具體細節(jié)可以看上面的分析。
measure()方法為final類型省艳,子類不能對其進行重寫measure()方法中主要就是對基本測量邏輯作判斷嫁审,最終會調(diào)用onMeasure()方法跋炕。

4.1.2 View的onMeasure

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

上面就一行代碼,看起來很簡單律适,但是包含了三個方法:getSuggestedMinimumWidth()/getSuggestedMinimumHeight()辐烂、getDefaultSize()setMeasuredDimension()捂贿,我們分開來看:

4.1.3 View的getSuggestedMinimumWidth

getSuggestedMinimumHeight()getSuggestedMinimumWidth()同理纠修,我們這里只看下getSuggestedMinimumWidth()

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

如果View沒有設(shè)置背景,那么View的寬度為mMinWidth厂僧,而mMinWidth就是android:minWidth屬性所指定的值,若android:minWidth沒有指定扣草,則默認為0;
如果View設(shè)置了背景,那么View的寬度為mMinWidthmBackground.getMinimumWidth()中的最大值颜屠。

那么mBackground.getMinimumWidth()的值是多少呢辰妙,我們來看看:

4.1.4 Drawable的getMinimumWidth

    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();//返回背景圖Drawable的原始寬度
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

可以看出:mBackground.getMinimumWidth()返回的是背景圖Drawable的原始寬度,若無原始寬度則返回0甫窟。

那么Drawable什么情況下有原始寬度密浑?如:ShapeDrawable沒有,但BitmapDrawable有粗井。

4.1.5 View的getDefaultSize

我們再來看看ViewgetDefaultSize()方法:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);//獲取測量模式
        int specSize = MeasureSpec.getSize(measureSpec);//獲取測量大小

        switch (specMode) {
        //測量模式為UNSPECIFIED時尔破,View的測量大小為默認大小,即getSuggestedMinimumWidth()/getSuggestedMinimumHeight()返回的結(jié)果浇衬。
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //測量模式為AT_MOST懒构、EXACTLY時,View的測量大小為測量規(guī)格中的測量大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

4.1.6 View的setMeasuredDimension

再來看setMeasuredDimension()

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        //...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

setMeasuredDimension()里會調(diào)用setMeasuredDimensionRaw()

4.1.7 View的setMeasuredDimensionRaw

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

setMeasuredDimensionRaw()中就是對測量出的寬高進行保存径玖。
到這里痴脾,單一Viewmeasure過程就完成了。

4.1.8 時序圖

最后梳星,來張相關(guān)的時序圖:

image

4.1.9 流程圖

以及來張測量過程細節(jié)流程圖:

image

4.2 ViewGroup的measure過程

由于ViewGroup包含了子View赞赖,因此其measure過程是通過遍歷所有的子View并對子View測量滚朵,然后合并所有子View的尺寸,最后得到ViewGroup的測量值前域。

image

如上圖所示辕近,要執(zhí)行ViewGroupmeasure,首先從頂部的ViewGroup開始遍歷匿垄,自上而下遞歸執(zhí)行子Viewmeasure移宅。

ViewGroupmeasure過程是從measureChildren()開始的。

4.2.1 ViewGroup的measureChildren

    /**
     *
     * @param widthMeasureSpec ViewGroup的寬測量規(guī)格
     * @param heightMeasureSpec ViewGroup的高測量規(guī)格
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {//遍歷子View
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

再來看measureChild()

4.2.2 ViewGroup的measureChild

    protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();//獲得子view的布局參數(shù)

        //獲得子view的寬/高測量規(guī)格
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        //子view開始測量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

getChildMeasureSpec()的代碼請看上面的分析椿疗。

4.2.3 View的measure

   public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        //...
   }

這里的measure跟上面單一Viewmeasure方法是一樣的漏峰。

4.2.4 ViewGroup的onMeasure

如果子View是一個單一的View的話,則直接調(diào)用ViewonMeasure()方法届榄,跟上面單一Viewmeasure過程是一樣大的;
如果子ViewViewGroup的話浅乔,理論上應(yīng)該是應(yīng)該調(diào)用ViewGrouponMeasure()方法的。
但實際上铝条,ViewGroup中是沒有onMeasure()這個方法的靖苇,ViewGroup作為一個抽象類,需要其子類去實現(xiàn)測量過程的onMeasure()方法班缰,比如LinearLayout贤壁、RelativeLayout等。因為LinearLayout埠忘、RelativeLayout這些布局具體不同的特性脾拆,其測量細節(jié)各有不同,無法進行統(tǒng)一的實現(xiàn)给梅,因此需要其子類去進行具體的實現(xiàn)假丧。因此我們自定義ViewGroup時需要重寫onMeasure()方法。
我們這里看下LinearLayoutonMeasure()實現(xiàn):

4.2.5 LinearLayout的onMeasure

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

LinearLayout會區(qū)分方向來進行不同的測量方法动羽,我們主要看下豎向的測量measureVertical()包帚,橫向的原理差不多這里就不看了。

4.2.6 LinearLayout的measureVertical

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

        //獲取子view數(shù)量
        final int count = getVirtualChildCount();

        //獲取LinearLayout的寬/高測量模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //...

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

            //..

            // 最終會調(diào)用子View的measure()运吓,計算出子View的大小
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

            // 獲取子View的測量高度
            final int childHeight = child.getMeasuredHeight();

            //..

            final int totalLength = mTotalLength;

            // 將子View的測量高度以及Margin加到LinearLayout的總高度上
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
        }

        //..

        // 加上LinearLayout設(shè)置的Padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);

        //..

        //保存測量值
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);

    }

最后會調(diào)用setMeasuredDimension()來保存測量好的值渴邦,至此ViewGroup的測量過程就完成了。

4.2.7 時序圖

最后拘哨,來張相關(guān)的時序圖:

image

4.2.8 流程圖

以及來張ViewGroup測量過程細節(jié)流程圖:

image

5. 自定義View

5.1 自定義單一View

自定義單一View谋梭,可以直接使用View中默認定義的onMeasure()方法。如果有時需要更精準(zhǔn)的測量倦青,可以重寫onMeasure()瓮床。

5.2 自定義ViewGroup

由于ViewGroup沒實現(xiàn)onMeasure(),所以自定義ViewGroup需要重寫onMeasure()方法。這里給個簡單的模板:

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

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

        //實現(xiàn)測量方法以及具體的測量細節(jié)
        measureMethod();

        //必不可少  保存測量后View寬和高的值
        setMeasuredDimension(widthMeasure, heightMeasure);
    }

6. 其他問題

6.1 獲取View的測量寬高

我們可以使用getMeasuredWidth()getMeasuredHeight()來獲取View測量出的寬高隘庄。

但是從上面的代碼分析可以看到踢步,在調(diào)用setMeasuredDimension()方法之后,我們才能使用getMeasuredWidth()getMeasuredHeight()來獲取View測量出的寬高丑掺,在這之前去調(diào)用這兩個方法得到的值都會是0获印。

結(jié)合Activity的啟動過程以及生命周期,在onCreate()onResume()中街州,都還未開始進行測量的操作兼丰,所以這時候調(diào)用getMeasuredWidth()getMeasuredHeight()的值都會是0。開始測量的相關(guān)代碼最早可以追涉到ActivityThreadhandleResumeActivity()方法唆缴,可以看下這篇文章的介紹:Activity啟動過程詳解鳍征。

另外,在某些情況下面徽,需要多次測量才能確定View最終寬高蟆技;因此這種情況下獲得的值可能是不準(zhǔn)確的,建議在layout過程中onLayout()去獲取最終寬高斗忌。

6.2 自定義View時WRAP_CONTENT不生效的問題

從上面getDefaultSize()方法的代碼可以看到,無論是AT_MOST模式還是EXACTLY模式旺聚,View的測量大小都為測量規(guī)格中的測量大小织阳,所以我們就會看到自定義View使用WRAP_CONTENT會起到跟MATCH_PARENT的效果。

為了解決這個問題需要在LayoutParams屬性為WRAP_CONTENT時指定一下默認的寬和高砰粹,如:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width=100;//設(shè)置一個默認寬
        int height=100;//設(shè)置一個默認高

        if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(width, height);//寬高都為WRAP_CONTENT都設(shè)置為默認寬高
        } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(width, heightSize);//只有寬為WRAP_CONTENT時唧躲,只設(shè)置默認寬
        } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(widthSize, height);//只有高為WRAP_CONTENT時,只設(shè)置默認高
        }
    }

這里的默認值請根據(jù)實際情況去確認碱璃。
像系統(tǒng)的TextView這些都支持WRAP_CONTENT弄痹,可以去看下其onMeasure()的源碼實現(xiàn)。
如果我們自定義的View只需要設(shè)置指定的具體寬高或者MATCH_PARENT嵌器,可以不用重寫onMeasure()方法肛真。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市爽航,隨后出現(xiàn)的幾起案子蚓让,更是在濱河造成了極大的恐慌,老刑警劉巖讥珍,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件历极,死亡現(xiàn)場離奇詭異,居然都是意外死亡衷佃,警方通過查閱死者的電腦和手機趟卸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锄列,你說我怎么就攤上這事图云。” “怎么了右蕊?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵琼稻,是天一觀的道長。 經(jīng)常有香客問我饶囚,道長帕翻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任萝风,我火速辦了婚禮嘀掸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘规惰。我一直安慰自己睬塌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布歇万。 她就那樣靜靜地躺著揩晴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贪磺。 梳的紋絲不亂的頭發(fā)上硫兰,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音寒锚,去河邊找鬼劫映。 笑死,一個胖子當(dāng)著我的面吹牛刹前,可吹牛的內(nèi)容都是我干的泳赋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼喇喉,長吁一口氣:“原來是場噩夢啊……” “哼祖今!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起轧飞,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衅鹿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后过咬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體大渤,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年掸绞,在試婚紗的時候發(fā)現(xiàn)自己被綠了泵三。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耕捞。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烫幕,靈堂內(nèi)的尸體忽然破棺而出俺抽,到底是詐尸還是另有隱情,我是刑警寧澤较曼,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布磷斧,位于F島的核電站,受9級特大地震影響捷犹,放射性物質(zhì)發(fā)生泄漏弛饭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一萍歉、第九天 我趴在偏房一處隱蔽的房頂上張望侣颂。 院中可真熱鬧,春花似錦枪孩、人聲如沸憔晒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拒担。三九已至,卻和暖如春攻询,著一層夾襖步出監(jiān)牢的瞬間澎蛛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工蜕窿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呆馁。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓桐经,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浙滤。 傳聞我的和親對象是個殘疾皇子阴挣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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