因为View的measure过程和Activity的生命周期方法不是同步执行的。虽然都是在主线中执行,但并没有明确的时序。
1. Activity/View#onWindowFocusChanged
View初始化完成会回调onWindowFocusChanged()方法。
2. view.post(runnable)
通过post可以将runnable投递到消息队列的尾部,然后等待Looper调用此runnable()的时候,View也已经初始化完成了。
3. ViewTreeObserver
使用ViewTreeObserver的众多回调可以完成这个功能。View中有ViewTreeObserver类的实例,获取该对象然后添加OnGlobalLayoutListener监听,当View树的状态发生改变或者View树内部的View的可见性发生变化时,onGlobalLayout()方法将被回调,因此这是获取View的宽高的一个好的时机。
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.adddOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.gerMeasureWidth();
int height = view.getMeasureHeight();
}
});
}
4. view.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对View进行measure来得到View的宽高。需要分情况。
- match_parent 直接放弃,无法measure出具体的宽高。原因是根据View的measure过程,构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间大小,而这个时候我们无法知道parentSize的值,所以理论上不可能测量出View的大小。
- 具体dp/px 比如宽高都是100px,如下measure:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
- wrap_content
如下measure:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
注意到(1 << 30) - 1,通过分析MeasureSpec的实现可以知道,View的尺寸使用30位二进制表示,也就是最大是30个1(即2^30 -1),也就是(1 << 30) -1,在最大化模式下,我们用View理论上能支持的最大值去构造MeasureSpec是合理的。