前言
前几天被问到了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 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- Android 面试文档分享:http://www.jianshu.com/p/8456fe6b27c4
网友评论