自定義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ī)格大小。來張圖解說明:

MeasureSpec組成圖解.png

3.2 SpecMode測量模式

測量模式分為三種UNSPECIFIED、EXACTLY、AT_MOST,其具體說明如下表所示:

測量模式 說明 應用場景
UNSPECIFIED (未指定) 父容器沒有對當前View有任何限制,當前View可以任意取尺寸 系統(tǒng)內(nèi)部
EXACTLY(精確) 父容器已經(jīng)確定當前View的大小,無論View想要多大都會在這范圍內(nèi) match_parent 和 具體數(shù)值
AT_MOST(最多) 當前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模式:父容器沒有對當前View有任何限制,當前View可以任意取尺寸
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

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

        //AT_MOST模式:當前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)換成對應的MeasureSpec,然后在onMeasure()方法中根據(jù)這個MeasureSpec來確定View的測量寬高。父容器所施加的規(guī)則對于DecorView與普通View是不同的,我們分開來看。

3.4.1 確定DecorView的MeasureSpec值

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

DecorView、ContentView、標題欄圖解.png

關(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ù)為準確的數(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 準確數(shù)值
MeasureSpec值 EXACTLY
windowSize(窗口大?。?/td>
AT_MOST
windowSize(不能超過窗口大?。?/td>
EXACTLY
rootDimension(準確數(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://當父容器模式為EXACTLY時
            if (childDimension >= 0) {// 當子view的LayoutParams>0,即有確切的值
                //子view大小為子自身所賦的值,模式大小為EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {// 當子view的LayoutParams為MATCH_PARENT時(-1)
                //子view大小為父容器剩余大小,模式為EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 當子view的LayoutParams為WRAP_CONTENT時(-2)
                //子view決定自己的大小,但最大不能超過父容器剩余大小,模式為AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST: // 當父容器的模式為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:// 當父容器的模式為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(父容器剩余大?。?/td>
AT_MOST
parentSize(不能超過父容器剩余大?。?/td>
UNSPECIFIED
targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值)
WRAP_CONTENT AT_MOST
parentSize(不能超過父容器剩余大?。?/td>
AT_MOST
parentSize(不能超過父容器剩余大小))
UNSPECIFIED
targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值)

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

  • 以子View布局參數(shù)為標準,橫向觀察:
  1. 當子View使用具體的dp/px時,無論父容器的測量模式是什么,子View的測量模式都是EXACTLY且大小等于設(shè)置的具體數(shù)值;
  2. 當子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是當前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)的時序圖:


單一View測量過程時序圖.png

4.1.9 流程圖

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


單一View測量過程流程圖.png

4.2 ViewGroup的measure過程

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

ViewGroup測量順序圖.png

如上圖所示,要執(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的話,理論上應該是應該調(diào)用ViewGrouponMeasure()方法的。
但實際上,ViewGroup中是沒有onMeasure()這個方法的,ViewGroup作為一個抽象類,需要其子類去實現(xiàn)測量過程的onMeasure()方法,比如LinearLayoutRelativeLayout等。因為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)的時序圖:

ViewGroup測量過程時序圖.png

4.2.8 流程圖

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

ViewGroup測量過程流程圖.png

5. 自定義View

5.1 自定義單一View

自定義單一View,可以直接使用View中默認定義的onMeasure()方法。如果有時需要更精準的測量,可以重寫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最終寬高;因此這種情況下獲得的值可能是不準確的,建議在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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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