为什么getHeight和getWidth的值会为零
measure过程完成了之后,可以通过getWidth和getHeight去正确获取到View的测量宽/高,但是在一些情况下onMeasure方法中拿到的测量宽高很可能是不准确的(PS:所以一些好的做法则是在onLayout方法中去获取View的测量宽高或者最终宽高)
而如果在onCreate,onResume,onStart方法中去获取View的宽高均为零,原因则是measure过程和Activity的生命周期是不同步的,因此如果没有测量完,那么这个宽高的值就会为零。
关于这个问题的几个解决方案
1)Activity/View#onWindowFocusChanged
这个方法的含义:view已经初始化完毕了,宽高都已经准备好了,这个时候去获取宽高信息就变得没有问题了,但是这个方法有一个不好的地方那就是当Activity的窗口得到焦点和失去焦点的时候都会去调用,所以如果频繁地调用onResume和onPause,那么这个方法也会被频繁地调用。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasureHeigh();
}
2)view.post(runnable)
通过post将一个runnable投递带消息队列的尾部,然后等待looper调用这个runnable的时候,view也已经初始化好了。
this.post(new Runnable()
{
@Override
public void run()
{
int width = view.getMeasureWidth();
int height = view.getMeasureHeigh();
}
});
3)ViewTreeObserver
使用ViewTreeObserver的这里其实采用观察者模式,注册一个观察者来监听view树,当view树的布局、视图树的焦点、视图树将要绘制、视图树滚动等发生改变时,ViewTreeObserver都会收到通知,ViewTreeObserver不能被实例化,可以调用View.getViewTreeObserver()来获得。它里面有许多回调:
public final class ViewTreeObserver {
// Recursive listeners use CopyOnWriteArrayList
private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private CopyOnWriteArrayList<OnEnterAnimationCompleteListener> mOnEnterAnimationCompleteListeners;
// Non-recursive listeners use CopyOnWriteArray
// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
// These listeners cannot be mutated during dispatch
private ArrayList<OnDrawListener> mOnDrawListeners;
}
OnGlobalLayoutt方法
其中当View树的状态发生改变或者View树的内部View发生了一些可见性的改变的时候,onGlobalLayout方法就会被调用,但是伴随着View树状态的改变,onGlobalLayout会被调用多次
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListenr(this);
int width = view.getMeasureWidth();
int height = view.getMeasureHeigh();
}
});
4)view.measure(int widthMeasureSpec,int heightMeasureSpec)
通过手动对View进行measure来得到view的宽高。
情况一:match_parent:
没有办法measure出具体的宽高,因为构造这种MeasureSpec需要parentSize,而在这个时候不知道parentSized的
PS:关于MeasureSpec的生成规则
情况二:EXACTLY模式
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width's dp,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height's dp,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
情况三:wrap_content
利用最大值构建MeasureSpec
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1>>30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1>>30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
附加:获取固定宽高
如果要获取的view的width和height是固定的,那么你可以直接使用:
View.getMeasureWidth()
View.getMeasureHeight()
小结
View的大小由width和height决定。一个View实际上同时有两种width和height值
第一种是measure width和measure height。他们定义了view想要在父View中占用多少width和height(详情见Layout)。measured height和width可以通过getMeasuredWidth() 和 getMeasuredHeight()获得。
第二种是width和height,有时候也叫做drawing width和drawing height。这些值定义了view在屏幕上绘制和Layout完成后的实际大小。这些值有可能跟measure width和height不同。width和height可以通过getWidth()和getHeight获得。