View的getWidth()和getMeasuredWidth()有什么区别吗?
View的高宽是由View本身和Parent容器共同决定的。getMeasuredWidth()
和getWidth()
分别对应于视图绘制的measure和layout阶段。getMeasuredWidth()
获取的是View原始的大小,也就是这个View在XML文件中配置或者是代码中设置的大小。getWidth()
获取的是这个View最终显示的大小,这个大小有可能等于原始的大小,也有可能不相等。比如说,在父布局的onLayout()
方法或者该View的onDraw()
方法里调用measure(0, 0)
,二者的结果可能会不同(measure
中的参数可以自己定义)。
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
之间的差值计算出来的,需要在布局之后才能确定它们的坐标,也就是说布局后在onLayout()
方法里才能调用getWidth()
来获取。因此,getWidth()
获取的宽度是在View设定好布局后整个View的宽度。
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;
}
从源码上看,getMeasuredWidth()
获取的是mMeasuredWidth
的这个值。这个值是一个8位的十六进制的数字,高两位表示的是这个measure阶段的Mode的值,具体可以查看MeasureSpec的原理。这里mMeasuredWidth & MEASURED_SIZE_MASK
表示的是测量阶段结束之后,View真实的值。而且这个值会在调用了setMeasuredDimensionRaw()
函数之后会被设置。所以getMeasuredWidth()
的值是measure阶段结束之后得到的View的原始的值。
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;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
总结一下,getMeasuredWidth
是measure阶段获得的View的原始宽度,getWidth
是layout阶段完成后,其在父容器中所占的最终宽度
如何在onCreate中拿到View的宽度和高度?
在onCreate()中获取View的高宽有三种方法:
- View.post(runnable)
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
int measuredWidth = view.getMeasuredWidth();
Log.i(TAG, "width: " + width);
Log.i(TAG, "measuredWidth: " + measuredWidth);
}
});
利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。借此机制,巧妙获取View的高宽属性,代码简洁,相比ViewTreeObserver监听处理,还不需要手动移除观察者监听事件。
- ViewTreeObserver.addOnGlobalLayoutListener()
监听View的onLayout()
绘制过程,一旦layout触发变化,立即回调onLayoutChange
方法。
注意,使用完也要主要调用removeOnGlobalListener()
方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能。
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.i(TAG, "width: " + view.getWidth());
Log.i(TAG, "height: " + view.getHeight());
}
});
- View.measure(int widthMeasureSpec, int heightMeasureSpec)
除了在onCreate()
中获得View的高宽,还可以在Activity的onWindowFocusChanged()
方法中获得高宽。