View 繪製體系知識梳理(7) getMeasuredWidth 和 getWidth 的區別

澤毛發表於2017-12-21

前言

前幾天被問到了getMeasuredWidthgetWidth之間的區別,因此回來看了一下原始碼,又順便複習了一遍measure/layout/draw的過程,有興趣的同學可以看前面的幾篇文章

一、getMeasuredWidth 和 getWidth 的定義

1.1 getMeasuredWidth

我們來看一下getMeasuredWidth方法的內部實現:

    /**
     * Like {@link #getMeasuredWidthAndState()}, but only returns the
     * raw width component (that is the result is masked by
     * {@link #MEASURED_SIZE_MASK}).
     *
     * @return The raw measured width of this view.
     */
    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
複製程式碼

這裡可以看到,該方法返回的是setMeasuredDimensionRaw中設定的mMeasuredWidthsize部分,也就是說,該方法返回的是在 測量階段中計算出的寬度

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

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
    }
複製程式碼

1.2 getWidth

    /**
     * Return the width of the your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }
複製程式碼

getWidth的值是根據mRightmLeft之間的差值計算出來的,在setFrame方法中,會對View的四個點座標mLeft/mRigth/mTop/mBottom進行賦值,它決定了View在其父容器中所處的位置:

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;

            //....
        }
        return changed;
    }
複製程式碼

setFrame就是在layout過程中呼叫的:

    public void layout(int l, int t, int r, int b) {

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //通過之前介紹的 setFrame 方法進行賦值。
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

    }
複製程式碼

1.3 小結

在之前的兩篇文章 View 繪製體系知識梳理(3) - 繪製流程之 Measure 詳解View 繪製體系知識梳理(4) - 繪製過程之 Layout 詳解,我們介紹了measurelayout的內部實現,而getMeasuredWidthgetWidth就分別對應於上面這兩個階段獲得的寬度,也就是說:

  • getMeasuredWidthmeasure階段獲得的View的原始寬度。
  • getWidthlayout階段完成後,其在父容器中所佔的最終寬度

1.4 註釋說明

下面是Google文件中對於上面這兩個方法的註釋說明:

  • getMeasuredWidth
The width of this view as measured in the most recent call to measure(). 
This should be used during measurement and layout calculations only. 
Use getWidth() to see how wide a view is after layout.

Returns: the measured width of this view
複製程式碼
  • getWidth
Return the width of the your view.

Returns: the width of your view, in pixels
複製程式碼

二、示例

我們用一個簡單的示例,來演示getMeasuredWidthgetWidth的區別:

2.1 佈局定義

首先定義一個自定義的LinearLayout,它包含兩個子View,在xml中它們的寬度都被指定為200dp

<?xml version="1.0" encoding="utf-8"?>
<com.demo.lizejun.repoopt.WidthDemoLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:background="@android:color/holo_blue_bright"
        android:layout_width="200dp"
        android:layout_height="100dp"/>
    <View
        android:background="@android:color/holo_orange_light"
        android:layout_width="200dp"
        android:layout_height="100dp"/>
</com.demo.lizejun.repoopt.WidthDemoLayout>
複製程式碼

2.2 重寫 onLayout 方法

WidthDemoLayout中,我們重寫它的onLayout方法,對它的子View重新擺放,並列印出getMeasuredWidthgetWidth方法的值:

public class WidthDemoLayout extends LinearLayout {

    public WidthDemoLayout(Context context) {
        super(context);
    }

    public WidthDemoLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WidthDemoLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (i == childCount - 1) {
                child.layout(child.getLeft() ,child.getTop(), child.getRight() + 400, child.getBottom());
            }
            Log.d("WidthDemoLayout", "measuredWidth=" + child.getMeasuredWidth() + ",width=" + child.getWidth());
        }
    }
}
複製程式碼

輸出的結果為:

>> 12-04 12:48:58.788 24935-24935/com.demo.lizejun.repoopt D/WidthDemoLayout: measuredWidth=800,width=800
>> 12-04 12:48:58.788 24935-24935/com.demo.lizejun.repoopt D/WidthDemoLayout: measuredWidth=800,width=1200
複製程式碼

最終展示的時候,雖然我們在xml中指定了相同的寬度,但是最終顯示是以getWidth為準:

View 繪製體系知識梳理(7)   getMeasuredWidth 和 getWidth 的區別


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章