对于在Activity中的onCreate()方法中获取View的长宽为0的解决方法

概述

相信不少朋友么总会遇到一个问题吧:想在Activity中的onCreate方法中想初始化一个控件,于是就在onCreate方法中这么写道:

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取view的长宽并打印
System.out.println("view.getWidth() = " + view.getWidth());
System.out.println("view.getHeight() = " + view.getHeight());
}
}

但是你会发现打印出来会出现这个结果:

1
2
01-26 18:33:01.297 28386-28386/? I/System.out: view.getWidth() = 0
01-26 18:33:01.297 28386-28386/? I/System.out: view.getHeight() = 0

特别蛋疼,是不是?

问题原因

这是因为在onCreate()方法中,activity设置了contentView后图像并不能第一时间就测量和绘制出来,此时用到这两个方法就会出现返回值为0的情况.

解决方法

异步判断获取法(不推荐)

当时我的第一个想法就是新开一个线程,然后每隔一定的时间进行判断,判断获取长宽是否为0,直到不为0则跳出循环判断,类似代码如下:

1
2
3
4
5
6
7
8
9
10
11
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (view.getWidth() != 0 && view.getHeight() != 0) {
//Next step
break;
}
}
}
}).start();

这样看似解决了问题,但是又出现了一个问题,就是这是个死循环,不加延时的话会特别占内存且特别卡,于是又想到了这样处理,在每次判断后延时100毫秒再进行下一次循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (view.getWidth() != 0 && view.getHeight() != 0) {
//Next step
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
}
}).start();

这样就能很好的解决了长宽的问题了,但是有没有发现一个比较蛋疼的地方呢?就是每100毫秒进行一次判断.

如果我101毫秒就读取完毕呢?那就浪费了99毫秒的时间,这个时间内用户可能会看到你没处理好的内容,体验就不会不太友好.
而这时间也不是固定的,不能通过测试获取到这个时间,每台机器的性能也不同而异,时间长短肯定也不是一致的.
所以这个方法是不推荐使用的

使用ViewTreeObserver来监听获取

先贴代码:

1
2
3
4
5
6
7
8
9
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
System.out.println("view.getWidth() = " + view.getWidth());
System.out.println("view.getHeight() = " + view.getHeight());
//这里记得要改成true,不然onDraw方法就不会调用了.
return true;
}
});

这个方法类似于观察者模式,也比较好的能获取到长宽值,而且类如其名,onPreDraw,在绘制前调用.
但是经过调试,我们还会发现一个小问题:

1
2
3
4
5
6
01-26 18:55:50.962 29438-29438/? I/System.out: view.getWidth() = 1056
01-26 18:55:50.962 29438-29438/? I/System.out: view.getHeight() = 1341
01-26 18:55:51.007 29438-29438/? I/System.out: view.getWidth() = 1056
01-26 18:55:51.008 29438-29438/? I/System.out: view.getHeight() = 1341
01-26 18:55:51.127 29438-29438/? I/System.out: view.getWidth() = 1056
01-26 18:55:51.127 29438-29438/? I/System.out: view.getHeight() = 1341

竟然会出现多次初始化!
这就是在View中的onDraw方法频繁调用的原因了,所以这个方法也是不错的,但是需要一个Flag判断是不是第一次初始化,也比较麻烦.

自定义View回调接口法

名副其实,如果这个View是自定义的View,我们完全可以自定义一个回调接口在View内部如下:

1
2
3
public interface OnSizeInitListener{
void onsizeInit(int width,int height);
}

并定义好成员对象:

1
private OnSizeInitListener onSizeInitListener;

暴露setter接口给外来对象:

1
2
3
4

public void setOnSizeInitListener(OnSizeInitListener onSizeInitListener) {
this.onSizeInitListener = onSizeInitListener;
}

与此同时,重写onSizeChanged方法,回调给监听对象:

1
2
3
4
5
6
7

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if(onSizeInitListener!= null)
onSizeInitListener.onsizeInit(w,h);
super.onSizeChanged(w, h, oldw, oldh);
}

别忘了在Activity中设置监听:

1
2
3
4
5
6
7
view.setOnSizeInitListener(new Clock.OnSizeInitListener() {
@Override
public void onSizeInit(int width, int height) {
System.out.println("width = " + width);
System.out.println("height = " + height);
}
});

这样也就能获取到View的长和宽了.
但是可能你又会问到了:

  1. 好像和第二种方法没什么区别呀,而且监听的方法比第二种方法还少?
    确实是这样,而且想额外监听其他方法的话还要重写其他接口,会更加麻烦,但是你能在内部定义第二种方法没有的Flag,实现初始化判断,毕竟onSizeChanged调用的次数有时也会很频繁!
  2. 如果是原生的View,这个方法不就没有用了吗?
    此时我们可以换一种思路,新建一个MyView来进行包装原生的View(类似于装饰者模式),或者使用继承的方式去给这个View进行设置监听

用View的post方法来回调获取(强烈推荐)

代码如下:

1
2
3
4
5
6
7
view.post(new Runnable() {
@Override
public void run() {
System.out.println("view.getWidth() = " + view.getWidth());
System.out.println("view.getHeight() = " + view.getHeight());
}
});

看起来是不是比第一种方法简单粗暴许多?
这样就能在Handler和Message的配合下,在视图创建完成后获取其长宽值了,而且不会多次进行初始化.

总结

上面的解决方案是我目前接触到的四种解决方案,其中第一种是我一开始不成熟的解决方法,大家可以忽略~~
经过对比,大家也可以看出第四种比较方便,而且代码量也很少,极其适合用来做onCreate方法中第一次初始化时候的获取长宽,在长宽不会变化的View中适用.
第三种自由度高,可以用来定制监听,比如说大小变化后也能快速的进行相应的操作,是不错的方法~
至于第二种和第一种,大家见仁见智啦~

如果有更好的获取长宽方法,欢迎和我交流,或者在博客下面留言~