我們都知道在onCreate()里面獲取控件的高度是0,這是為什么呢?我們來看一下示例:
首先我們寫一個控件
public class MyImageView extends ImageView {
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
System.out.println("onMeasure 我被調用了" + System.currentTimeMillis());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
System.out.println("onDraw 我被調用了" + System.currentTimeMillis());
}
}
布局文件:
<com.test.MyImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/test" />
測試的Activity的onCreate():
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
System.out.println("執行完畢.."+System.currentTimeMillis());
}
運行結果:
說明等onCreate方法執行完了,我們定義的控件才會被測量(measure),所以我們在onCreate方法里面通過view.getHeight()獲取控件的高度或者寬度肯定是0,因為它自己還沒有被測量,也就是說他自己都不知道自己有多高,而你這時候去獲取它的尺寸,肯定是不行的。
解決方案一
使用view的measure方法
優點:可以立即獲得寬和高
缺點:人為的多了一次測量過程
這種方法適用于需要在onCreate完成之前就獲得一個view的寬和高的情況。
比如獲得一個LinearLayout的寬和高
public int getViewWidth(LinearLayout view) {
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
return view.getMeasuredWidth();
}
public int getViewHeight(LinearLayout view) {
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
return view.getMeasuredHeight();
}
這種方法的原理是直接調用一個view或者viewgroup的measure方法去測量,測量之后該view的getMeasuredHeight()就會返回剛才測量所得的高,getMeasuredWidth返回測量所得寬。
本來在布局加載的過程中,view的measure方法一定會被系統調用(在onResume中已經調用了measure方法),但這發生在我們所不知道的某個時間點,為了在這之前提前得到測量結果,我們主動調用measure方法,但是這樣做的好處是可以立即獲得寬和高,壞處是多了一次測量過程。
至于為什么參數是LayoutParams.WRAP_CONTENT,那是因為我假設這個view的layout_width和layout_height為wrap_content,因為如果為一個確切的值,還有必要測量嗎?
解決方案二
利用ViewTreeObserver的OnGlobalLayoutListener
優點:不需要額外的測量過程
缺點:只有在布局加載完成后,才能得到寬和高
OnGlobalLayoutListener是ViewTreeObserver的內部類,當一個視圖樹的布局發生改變時,可以被ViewTreeObserver監聽到,這是一個注冊監聽視圖樹的觀察者(observer),在視圖樹的全局事件改變時得到通知。ViewTreeObserver不能直接實例化,而是通過getViewTreeObserver()獲得。
其中,我們可以利用OnGlobalLayoutListener來獲得一個視圖的真實高度,但是需要注意的是OnGlobalLayoutListener會被多次觸發,因此在得到了高度之后,要將OnGlobalLayoutListener清除掉。
private void measureWidth() {
//對控件layout結束事件進行監聽(也不知道到底是先布局子空間還是父控件)
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {// 當layout執行結束后回調
//使用完必須撤銷監聽(只測量一次),否則,會一直不停的不定時的測量,這比較耗性能
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);//Added in API level 16
//view.getViewTreeObserver().removeGlobalOnLayoutListener(this);//廢棄了
int width = view.getMeasuredWidth();
int width2 = view.getWidth();//和上面的值是一樣的
}
});
}
這種方法無法像第一種方法那樣通過一個函數返回值,因為他是基于listener的,OnGlobalLayoutListener的onGlobalLayout被回調之前是沒有值的。由于布局狀態可能會發生多次改變,因此OnGlobalLayoutListener的onGlobalLayout可能被回調多次,所以我們在 第一次獲得值之后就將listener注銷掉。
**其他相關回調接口
**
ViewTreeObserver的內部回調接口:
OnGlobalLayoutListener 當在一個視圖樹中全局布局發生改變或者視圖樹中的某個視圖的可視狀態發生改變時回調
OnPreDrawListener 當一個視圖樹將要繪制時,所要調用的回調函數的接口類
OnScrollChangedListener 當一個視圖樹中的一些組件發生滾動時回調
OnGlobalFocusChangeListener 當在一個視圖樹中的焦點狀態發生改變時回調
OnTouchModeChangeListener 當一個視圖樹的觸摸模式發生改變時回調
回調方法:
addOnGlobalFocusChangeListener 當在一個視圖樹中的焦點狀態發生改變時調用
addOnGlobalLayoutListener 當在一個視圖樹中某個視圖的可視狀態發生改變時調用
addOnPreDrawListener 當一個視圖樹將要繪制時調用
addOnScrollChangedListener 當一個視圖發生滾動時調用
addOnTouchModeChangeListener 當一個觸摸模式發生改變時調用
dispatchOnGlobalLayout 當整個布局發生改變時通知相應的注冊監聽器
dispatchOnPreDraw 當一個視圖樹將要繪制時通知相應的注冊監聽器
isAlive 指示當前的ViewTreeObserver是否可用
removeGlobalOnLayoutListener 移除之前已經注冊的全局布局回調函數
removeOnGlobalFocusChangeListener 移除之前已經注冊的焦點改變回調函數
removeOnPreDrawListener 移除之前已經注冊的預繪制回調函數
removeOnScrollChangedListener 移除之前已經注冊的滾動改變回調函數
removeOnTouchModeChangeListener 移除之前已經注冊的觸摸模式改變回調函數