1、View的getWidth()和getMeasuredWidth()有什麼區別嗎?
View 的寬高是由 View 本身和 parent 容器共同決定的。
getMeasuredWidth() 與 getWidth() 分別對應於檢視繪製 的 measure 與 layout 階段。 getMeasuredWidth()(int widthMeasureSpec, int heightMeasureSpec)方法裡面獲得的,而getWidth()是在onLayout()方法中獲得的。大部分情況下他們是相等,也有一些不相等的情況。比如在父佈局的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()返回的是右邊座標減輕座標減去左邊座標,這要在佈局之後才能確定它們的座標,也就是說在佈局後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()返回的是此檢視的原始測量寬度。所以說getMeasuredWidth()是對View上的內容進行測量後得到的View內容佔據的寬度。
一般情況下,getMeasuredWidth()和getWidth()獲得的結果是一樣的,但是如果在父佈局的onLayout()方法或者此View的onDraw()方法裡呼叫measure(0,0);(measure中的引數的值你自己可以定義)的話,兩者的結果可能會不同。
例如:
public class CustomView extends android.support.v7.widget.AppCompatTextView {
private String TAG = CustomView.class.getSimpleName();
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, 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 left, int top, int right, int bottom) {
super.onLayout(changed, left + 10, top, right + 10, bottom + 10);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
measure(0, 0);
Log.d(TAG, "width=" + getWidth());
Log.d(TAG, "height=" + getHeight());
Log.d(TAG, "MeasuredWidth=" + getMeasuredWidth());
Log.d(TAG, "MeasuredHeight=" + getMeasuredHeight());
}
}
複製程式碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context="com.shzx.viewdemo.MainActivity">
<com.shzx.viewdemo.CustomView
android:id="@+id/customView"
android:layout_width="100dp"
android:text="ceshi"
android:layout_height="100dp" />
</LinearLayout>
複製程式碼
此時的列印結果是
12-16 07:13:40.050 8901-8901/com.shzx.viewdemo D/CustomView: width=350
12-16 07:13:40.050 8901-8901/com.shzx.viewdemo D/CustomView: height=350
12-16 07:13:40.050 8901-8901/com.shzx.viewdemo D/CustomView: MeasuredWidth=116
12-16 07:13:40.050 8901-8901/com.shzx.viewdemo D/CustomView: MeasuredHeight=66
複製程式碼
此時getWidth()和getMeasuredWidth()不相等
因此可以得出 getWidth(): View在設定好佈局後整個View的寬度。getMeasuredWidth(): 對View上的內容進行測量後得到的View內容佔據的寬度
2、如何在onCreate中拿到View的寬度和高度?
在onCreate()中獲取View的寬高有三種方法: 獲取view的寬高
private void getWidthHeight() {
int width = customView.getWidth();
int height = customView.getHeight();
LogUtils.e("width =" + width);
LogUtils.e("height =" + height);
int measuredWidth = customView.getMeasuredWidth();
int measuredHeight = customView.getMeasuredHeight();
LogUtils.e("measuredWidth =" + measuredWidth);
LogUtils.e("measuredHeight =" + measuredHeight);
}
複製程式碼
1)、View.post(runnable)
利用 Handler 通訊機制,傳送一個 Runnable 到 message queue 中,當 view layout 處理完成時,自動傳送訊息,通知 UI 執行緒。藉此機制,巧妙獲取 View 的寬高屬性。程式碼簡潔,使用簡單,相比 ViewTreeObserver 監聽處理,還不需要手動移除觀察者監聽事件。
customView.post(new Runnable() {
@Override
public void run() {
getWidthHeight();
}
});
複製程式碼
列印結果:
12-16 02:33:49.938 4238-4238/com.shzx.viewdemo E/MainActivity: width =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity$1,methodName=run,lineNumber=23 ]
12-16 02:33:49.941 4238-4238/com.shzx.viewdemo E/MainActivity: height =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity$1,methodName=run,lineNumber=24 ]
12-16 02:33:49.941 4238-4238/com.shzx.viewdemo E/MainActivity: measuredWidth =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity$1,methodName=run,lineNumber=28 ]
12-16 02:33:49.946 4238-4238/com.shzx.viewdemo E/MainActivity: measuredHeight =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity$1,methodName=run,lineNumber=29 ]
複製程式碼
2)、ViewTreeObserver.addOnGlobalLayoutListener(OnGlobalLayoutListener listener)
監聽 View 的 onLayout() 繪製過程,一旦 layout 觸發變化,立即回撥 onLayoutChange 方法。
注意,使用完也要注意呼叫 removeOnGlobalLayoutListener() 方法移除監聽事件。避免後續每一次發生全域性 View 變化均觸發該事件,影響效能。
ViewTreeObserver observer = customView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getWidthHeight();
customView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
複製程式碼
列印結果:
12-16 03:34:06.981 6116-6116/com.shzx.viewdemo E/MainActivity: width =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=61 ]
12-16 03:34:06.982 6116-6116/com.shzx.viewdemo E/MainActivity: height =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=62 ]
12-16 03:34:06.983 6116-6116/com.shzx.viewdemo E/MainActivity: measuredWidth =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=66 ]
12-16 03:34:06.983 6116-6116/com.shzx.viewdemo E/MainActivity: measuredHeight =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=67 ]
複製程式碼
3)、View.measure(int widthMeasureSpec,int heightMeasureSpec)
View.MeasureSpec.makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode)
其中 MeasureSpec.MODE_SHIFT = 30
size最大值為 (1<<30)-1, 即用30位二進位制表示,也就是說最大是30個1 (即2^30-1)。
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
int heigthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
customView.measure(widthMeasureSpec,heigthMeasureSpec);
int width = customView.getWidth();
int height = customView.getHeight();
LogUtils.e("width =" + width);
LogUtils.e("height =" + height);
int measuredWidth = customView.getMeasuredWidth();
int measuredHeight = customView.getMeasuredHeight();
LogUtils.e("measuredWidth =" + measuredWidth);
LogUtils.e("measuredHeight =" + measuredHeight);
複製程式碼
列印結果:
12-16 03:04:23.278 5444-5444/com.shzx.viewdemo E/MainActivity: width =0>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=42 ]
12-16 03:04:23.278 5444-5444/com.shzx.viewdemo E/MainActivity: height =0>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=43 ]
12-16 03:04:23.278 5444-5444/com.shzx.viewdemo E/MainActivity: measuredWidth =200>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=47 ]
12-16 03:04:23.278 5444-5444/com.shzx.viewdemo E/MainActivity: measuredHeight =200>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=48 ]
複製程式碼
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heigthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
customView.measure(widthMeasureSpec,heigthMeasureSpec);
int width = customView.getWidth();
int height = customView.getHeight();
LogUtils.e("width =" + width);
LogUtils.e("height =" + height);
int measuredWidth = customView.getMeasuredWidth();
int measuredHeight = customView.getMeasuredHeight();
LogUtils.e("measuredWidth =" + measuredWidth);
LogUtils.e("measuredHeight =" + measuredHeight);
複製程式碼
列印結果:
12-16 03:03:28.394 5344-5344/com.shzx.viewdemo E/MainActivity: width =0>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=42 ]
12-16 03:03:28.395 5344-5344/com.shzx.viewdemo E/MainActivity: height =0>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=43 ]
12-16 03:03:28.395 5344-5344/com.shzx.viewdemo E/MainActivity: measuredWidth =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=47 ]
12-16 03:03:28.395 5344-5344/com.shzx.viewdemo E/MainActivity: measuredHeight =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=onCreate,lineNumber=48 ]
複製程式碼
除了在onCreate()中獲得View的寬高,還可以在Activity的onWindowFocusChanged(boolean hasFocus)方法中獲取寬高
原始碼:
/**
* Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
*
* <p>Note that this provides information about global focus state, which
* is managed independently of activity lifecycles. As such, while focus
* changes will generally have some relation to lifecycle changes (an
* activity that is stopped will not generally get window focus), you
* should not rely on any particular order between the callbacks here and
* those in the other lifecycle methods such as {@link #onResume}.
*
* <p>As a general rule, however, a resumed activity will have window
* focus... unless it has displayed other dialogs or popups that take
* input focus, in which case the activity itself will not have focus
* when the other windows have it. Likewise, the system may display
* system-level windows (such as the status bar notification panel or
* a system alert) which will temporarily take window input focus without
* pausing the foreground activity.
*
* @param hasFocus Whether the window of this activity has focus.
*
* @see #hasWindowFocus()
* @see #onResume
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasFocus) {
}
複製程式碼
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
getWidthHeight();
}
複製程式碼
列印結果:
12-16 03:46:36.929 6425-6425/com.shzx.viewdemo E/MainActivity: width =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=58 ]
12-16 03:46:36.929 6425-6425/com.shzx.viewdemo E/MainActivity: height =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=59 ]
12-16 03:46:36.929 6425-6425/com.shzx.viewdemo E/MainActivity: measuredWidth =116>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=63 ]
12-16 03:46:36.929 6425-6425/com.shzx.viewdemo E/MainActivity: measuredHeight =66>>>[ threadID=1,threadName=main,fileName=MainActivity.java,className=com.shzx.viewdemo.MainActivity,methodName=getWidthHeight,lineNumber=64 ]
複製程式碼