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() {@Overridepublic 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() {@Overridepublic 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()
方法中獲得高寬。