android: View的getWidth() 和 getMeasureWidth()方法的區別
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階段完成後,其在父容器中所佔的最終寬度。
什麼時候不同呢?
首先自定義一個父佈局:CustomView
package com.yongdaimi.android.androidapitest.view; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.LinearLayout; import androidx.annotation.Nullable; public class CustomView extends LinearLayout { public static final String TAG = "xp"; private View mFirstView; private int mFirstViewWidth; private int mFirstViewHeight; private int mFirstViewMeasureWidth; private int mFirstViewMeasureHeight; public CustomView(Context context) { this(context, null); } public CustomView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setOrientation(LinearLayout.HORIZONTAL); } @Override protected void onFinishInflate() { super.onFinishInflate(); mFirstView = getChildAt(0); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mFirstViewWidth = mFirstView.getWidth(); mFirstViewHeight = mFirstView.getHeight(); mFirstViewMeasureWidth = mFirstView.getMeasuredWidth(); mFirstViewMeasureHeight = mFirstView.getMeasuredHeight(); Log.i(TAG, "onSizeChanged: mFirstViewWidth: " + mFirstViewWidth + ", mFirstViewHeight: " + mFirstViewHeight + ", mFirstViewMeasureWidth: " + mFirstViewMeasureWidth + ", mFirstViewMeasureHeight: " + mFirstViewMeasureHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mFirstView.layout(-100, 0, -50, 50); mFirstViewWidth = mFirstView.getWidth(); mFirstViewHeight = mFirstView.getHeight(); mFirstViewMeasureWidth = mFirstView.getMeasuredWidth(); mFirstViewMeasureHeight = mFirstView.getMeasuredHeight(); Log.i(TAG, "onLayout: mFirstViewWidth: " + mFirstViewWidth + ", mFirstViewHeight: " + mFirstViewHeight + ", mFirstViewMeasureWidth: " + mFirstViewMeasureWidth + ", mFirstViewMeasureHeight: " + mFirstViewMeasureHeight); } }
然後在activity的佈局檔案使用這個佈局並新增一個新的控制元件:
<?xml version="1.0" encoding="utf-8"?> <com.yongdaimi.android.androidapitest.view.CustomView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_first" android:layout_width="60dip" android:layout_height="60dip" android:background="@android:color/holo_green_light" /> </com.yongdaimi.android.androidapitest.view.CustomView>
執行:
2020-09-04 10:56:51.202 30999-30999/com.yongdaimi.android.androidapitest I/xp: onSizeChanged: mFirstViewWidth: 0, mFirstViewHeight: 0, mFirstViewMeasureWidth: 180, mFirstViewMeasureHeight: 180
2020-09-04 10:56:51.204 30999-30999/com.yongdaimi.android.androidapitest I/xp: onLayout: mFirstViewWidth: 50, mFirstViewHeight: 50, mFirstViewMeasureWidth: 180, mFirstViewMeasureHeight: 180
可以看到,只要在程式碼裡重新修改了子控制元件的擺放位置,getWidth和getMeasureWidth的值就會不同。
如何在onCreate中拿到View的寬度和高度?
在onCreate()中獲取View的高寬有三種方法:
1. 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監聽處理,還不需要手動移除觀察者監聽事件。
2.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()); } });
3. View.measure(int widthMeasureSpec, int heightMeasureSpec)
除了在onCreate()中獲得View的高寬,還可以在Activity的onWindowFocusChanged() 方法中獲得高寬。