android onCreate中獲取view寬高為0的多種解決方法
這個問題大家肯定遇到過不止一次,其實很簡單,解決它也很容易,但是咱們追求的畢竟不是解決它,而是找到幾種方法去解決,並且這麼解決的原理是什麼。
這裡列出4種解決方案:
Activity/View#onWindowFocusChanged
這個函式的含義是:view已經初始化完畢了,寬/高已經準備好了,這個時候去獲取寬高是可以成功獲取的。但是需要注意的是onWindowFocusChanged函式會被呼叫多次,當Activity的視窗得到焦點和失去焦點時均會被呼叫一次,如果頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地呼叫。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
view.post(runnable)
通過post可以將一個runnable投遞到訊息佇列的尾部,然後等待UI執行緒Looper呼叫此runnable的時候,view也已經初始化好了。
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
使用ViewTreeObserver的眾多回調可以完成這個功能,比如使用OnGlobalLayoutListener這個介面,當view樹的狀態發生改變或者view樹內部的view的可見性發生改變時,onGlobalLayout方法將被回撥,因此這是獲取view的寬高一個很好的時機。需要注意的是,伴隨著view樹的狀態改變等,onGlobalLayout會被呼叫多次。
v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
再來詳細介紹一下ViewTreeObserver這個類,這個類是用來註冊當view tree全域性狀態改變時的回撥監聽器,這些全域性事件包括很多,比如整個view tree檢視的佈局,檢視繪製的開始,點選事件的改變等等。還有千萬不要在應用程式中例項化ViewTreeObserver物件,因為該物件僅是由檢視提供的。
ViewTreeObserver類提供了幾個相關函式用來新增view tree的相關監聽器:
view.measure(int widthMeasureSpec, int heightMeasureSpec)
通過手動對view進行measure來得到view的寬/高,這種情況比較複雜,這裡要分情況處理,根據view的layoutparams來分:
match_parent
直接放棄,無法measure出具體的寬/高。原因很簡單,根據view的measure過程,構造此種MeasureSpec需要知道parentSize,即父容器的剩餘空間,而這個時候我們無法知道parentSize的大小,所以理論上不可能測量處view的大小。
wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
注意到(1<<30)-1,我們知道MeasureSpec的前2位為mode,後面30位為size,所以說我們使用最大size值去匹配該最大化模式,讓view自己去計算需要的大小。
具體的數值(dp/px)
這種模式下,只需要使用具體數值去measure即可,比如寬/高都是100px:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
原始碼和結果
demo程式碼如下
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/v_view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
<View
android:id="@+id/v_view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_launcher"/>
</LinearLayout>
activity:
public class MainActivity extends BaseActivity{
private View v_view1;
private View v_view2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
v_view1 = findViewById(R.id.v_view1);
v_view2 = findViewById(R.id.v_view2);
L.i("normal: v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
v_view1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view1.getMeasuredWidth():" + v_view1.getMeasuredWidth()
+ " v_view1.getMeasuredHeight():" + v_view1.getMeasuredHeight());
v_view2.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view2.getMeasuredWidth():" + v_view2.getMeasuredWidth()
+ " v_view2.getMeasuredHeight():" + v_view2.getMeasuredHeight());
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
}
log日誌:
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:28): normal: v_view1.getWidth():0 v_view1.getHeight():0
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:50): measure : v_view1.getMeasuredWidth():144 v_view1.getMeasuredHeight():144
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:53): measure : v_view2.getMeasuredWidth():16777215 v_view2.getMeasuredHeight():16777215
I/[PID:2659]: [TID:1] 2.onGlobalLayout(line:42): ViewTreeObserver : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] 1.run(line:34): post(Runnable) : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] MainActivity.onWindowFocusChanged(line:61): onWindowFocusChanged : v_view1.getWidth():144 v_view1.getHeight():144
介面:
小的為view_1,大的為view_2,從log日誌中就發現有問題了:view_2檢視使用measure之後計算出來的寬高是錯誤的,所以View類的檢視使用measure計算出來的結果是不準確的,這點需要特別特別注意。