前言
前幾天被問到了getMeasuredWidth
和getWidth
之間的區別,因此回來看了一下原始碼,又順便複習了一遍measure/layout/draw
的過程,有興趣的同學可以看前面的幾篇文章
- View 繪製體系知識梳理(3) - 繪製流程之 Measure 詳解
- View 繪製體系知識梳理(4) - 繪製過程之 Layout 詳解
- View 繪製體系知識梳理(5) - 繪製過程之 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
中設定的mMeasuredWidth
的size
部分,也就是說,該方法返回的是在 測量階段中計算出的寬度。
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
的值是根據mRight
和mLeft
之間的差值計算出來的,在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 詳解,我們介紹了measure
和layout
的內部實現,而getMeasuredWidth
和getWidth
就分別對應於上面這兩個階段獲得的寬度,也就是說:
getMeasuredWidth
是measure
階段獲得的View
的原始寬度。getWidth
是layout
階段完成後,其在父容器中所佔的最終寬度
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
複製程式碼
二、示例
我們用一個簡單的示例,來演示getMeasuredWidth
和getWidth
的區別:
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
重新擺放,並列印出getMeasuredWidth
和getWidth
方法的值:
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
為準:
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- Android 面試文件分享:www.jianshu.com/p/8456fe6b2…