View的getWidth()和getMeasuredWidth()有什么区别吗?
getWidth()的源码
public final int getWidth(){
return mRight - mLeft;
}
getMeasuredWidth()返回的是view的测量宽度,getWidth()返回的view的最终的宽度。从默认的实现可以看到,getWidth()方法的返回值刚好是View的测量宽度,所以这两个是相等的。只不过形成的时机不一样,getMeasuredWidth()形成于view的Measure过程,而最终的宽度形成于View的layout过程,也就是说两者的赋值时机是不同的。因此,在日常的开发中,我们可以认为view的测量宽度等于最终的宽度,不过的确存在某些特殊的情况会导致两者的结果不一致,以下例子可以说明:
public void layout(int l,int t,int r,int b){
super.layout(l,t,r+100,b+100);
}
上述代码会导致在任何情况下View的最终宽度总是比测量的宽度大100px,虽然这样会导致View显示不正常并且没有实际的意义,但是这也证明了测量的宽度可以不等于最终的宽度。还有在一些情况下view需要测量多次才可以确定自己的宽度,在几次测量过程中,测量的宽度可能与最终的宽度不一致,不过最终的宽度与实际的宽度还是相等的。
如何在onCreate中拿到view的宽度和高度?
在onCreate、onStart和onResume中均无法正确得到某个view的正确宽/高信息,这是因为View的measure过程和activity的生命周期不是同步执行的,因此无法保证Activity执行了onCreate、onStart和onResume时某个view已经测量完毕了,如果没有测量完毕的话,那么获取到的宽高就会为0。下面给出四种方式来解决这个问题:
Activity/View #onWindowFocusChanged.
onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽高已经准备好了,这个时候去获取
宽高是没有问题的。需要注意的是,onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点和失
去焦点时均会被调用一次。具体来说,当Activity继续执行或停止执行时,onWindowFocusChanged均会被调
用,如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用。典型代码如下:
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
view.post(r)
通过post可以将一个runnable投递到消息队列的队尾,然后等待Looper调用此runnable的时候,View也已经初始化好了。典型的代码如下:
protected void onStart(){
super.onStart();
view.post(new Runnable(){
@Override
public void run(){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
ViewTreeObserver
使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当View树的状态发生改变时,onGlobalLayout方法将被回调,
因此这是获取View的宽/高一个很好的时机。需要注意的是,伴随着View树的状态改变等,onGlobalLayout会被调用多次,典型代码如下:
protected void onStart(){
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addonGlobalLayoutListener(new OnGlobalLayoutListener(){
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout(){
if (Build.VERSION.SDK_INT < 16) {
removeLayoutListenerPre16(observer, this);
} else {
removeLayoutListenerPost16(observer this);
}
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
@SuppressWarnings("deprecation")
private void removeLayoutListenerPre16(ViewTreeObserver observer,
ViewTreeObserver.OnGlobalLayoutListener listener) {
observer.removeGlobalOnLayoutListener(listener);
}
@TargetApi(16)
private void removeLayoutListenerPost16(ViewTreeObserver observer,
ViewTreeObserver.OnGlobalLayoutListener listener) {
observer.removeOnGlobalLayoutListener(listener);
}
view.measure(int widthMeasureSpec,int heightMeasureSpec)
通过手动对view进行measure来得到View的宽/高。这种方法比较复杂,这里要分情况处理,根据View的LayoutParams来分:
match_parent
直接放弃,无法measure出具体的宽/高。构造此种MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。
具体的数值(dp/px)
比如宽高都是100px
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);
通过分析MeasureSpec的实现可以知道,View的尺寸可以使用30位二进制表示,也就是说最大是2^30-1,在最大化模式下,我们用View理论上能支持的最大
值去构造MeasureSpec是合理的。
关于View的measure,网络上有两个错误的用法。为什么说是错误的,首先其违背了系统的内部实现规范(因为无法通过错误的MeasureSpec去得出合法的
SpecMode,从而导致measure过程出错),其次不能保证一定能measure出正确的结果。
第一种错误的用法:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec,heightMeasureSpec);
第二种错误的用法:
view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);