android 获取View的宽高问题

1,881 阅读3分钟

通常情况下我们在onCreate()、onStart()、onResume()获取View的宽高都是0,那是因为View的measure过程和Activity的生命周期不是同步进行的,不能保证在Activity的onCreate()、onStart()、onResume()方法执行时View已经测量完毕,所以不能获取到正确的宽高。下面介绍以下几种获取View宽高的方法:

  • Activity/View的onWindowFocusChanged方法体内
  • View.post(runnable)方法/Handler.post(new Runnable)
  • ViewTreeObserver
  • View.measure
  • 自定义View时获取宽高
  • 通过LayoutParams获取

1、Activity/View的onWindowFocusChanged方法体内

可以在Activity或者View中重写该方法获取,该方法意思是View已经初始化完毕了,既然初始化完毕了自然就可以获取View的宽高了,但是该方法可能执行多次,当焦点变化时都会被调用。

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);
    if(hasWindowFocus){
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
}

2、View.post(runnable)方法/Handler.post(new Runnable)

该方式获取主要是通过view的post将一个Runnable添加到消息队列的尾部,等待Looper调用此Runnable的时候,view也已经初始化好了。同理通过Handler的post方式也是一样,不过Handler必须是主线程的Handler,可以在主线程创建也可以通过new Handler(Looper.getMainLooper)方式创建。

两者的相同之处都是往消息队列尾部添加消息等待执行,不同点是 View要执行必须要保证它已经attached到了Window上。

3、ViewTreeObserver

这个类是整个View树结构的观察者,可以通过该类的OnGlobalLayoutListener接口来获取。 因为当View树的内部结构的View可见性或者状体改变时该接口都会被调用。具体使用如下:

ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
});
注意:removeGlobalOnLayoutListener()方法已经过时,可以调用removeOnGlobalLayoutListener()方法,但是该方法要求API >= 16。

4、View.measure

完整方法名为measure(int widthMeasureSpec,int heightMesureSpec) 通过手动对view测量得到宽高,这种方法较为复杂,要分情况处理,根据view的LayoutParams来分:

  • match_parent

    根据View的measure过程分析构造此种MeasureSpec需要知道父容器的剩余空间,而此时无法知道,所以理论上不能测出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);
view.measure(widthMeasureSpec,heightMeasureSpec);
int width = view.getMeasuredWidth());
int height = view.getMeasuredHeight());

注意到(1 << 30)-1,通过分析MeasureSpec的实现可以知道,View的尺寸使用30位二进制表示,也就是说最大是30个1(即2^30 -1),也就是(1 << 30)-1,在最大化模式下我们用View理论上支持的最大值去构造MeasureSpec是合理的。

5、自定义View时获取宽高

有时在自定义控件的时候会需要用到宽高值,大部分的时候可以在onLayout方法里面获取到真实宽高值,同样也可以用上述几种方法获取。首先让自定义的View实现onGlobalListener接口,需要实现onGlobalLayout方法.
然后在onAttachedToWindow()添加

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    getViewTreeObserver().addOnGlobalLayoutListener(this);
}

记得要在onDetachedFromWindow()删除

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    getViewTreeObserver().removeOnGlobalLayoutListener(this);(API 16)
}

接着在onGlobalLayout方法中获取宽高即可,因为要调用多次,可以设立个标志位.

private boolean isFirst = true;
@Override
public void onGlobalLayout() {
    if (isFirst) {
        int height = getHeight();
        isFirst = false;
    }
}

6、LayoutParams获取

对于xml文件里面设置了具体宽高的view可以通过view.getLayoutParams().height/width获取到宽高。

优点:能及时获取到,操作简单;

缺点:不够通用,没有设置具体宽高的获取到的值就是0了。