转载请标明出处:一片枫叶的专栏
上一个github小项目中我们介绍了防止按钮重复点击的小框架,其实现的核心逻辑是重写OnClickListener的onClick方法,添加防止重复点击的逻辑,即为第二次点击与第一次点击的时间间隔添加阙值,若第二次点击的时间间隔与第一次点击的时间间隔小于阙值,则此次点击无效,再次基础上我们又封装了点击组件验证网络Listener,点击组件验证是否登录Listener等,具体可参考: github项目解析(七)–>防止按钮重复点击
本文中我将介绍一下Android中Activity启动时获取组件宽高的五种方式。我们知道,有时候我们需要在Activity启动的时候获取某一组件的宽或者是高用于动态的更改UI布局文件,但是这时候我们直接通过getWidth和getHeight方法获取是有问题的。为什么这么说呢?这里我们可以下一个测试的例子来验证一下:
问题:
- 在Activity的启动流程中通过getWidht和getHeight方法获取组件的宽度和高度
/**
* 在onCreate方法中调用,用于获取TextView的宽度和高度
*/
private void getTextHeightAndWidth() {
// 我们定义的用于获取宽度和高度的组件
titleText = (TextView) findViewById(R.id.text_title);
int height = titleText.getHeight();
int width = titleText.getWidth();
Log.i(TAG, "height:" + height + " " + "width:" + width);
}
这段代码看似是正常没有问题的,但是我们将调用的代码写在onCreate方法的时候,执行这段代码之后,打印的结果:
06-26 20:12:15.356 19453-19453/uuch.com.Android_viewheight I/MainActivity: height:0 width:0
咦?为什么打印的height和width都是0呢?我们在执行一遍呢?结果还是一样的,两个变量都是0,难道这段代码不能再onCreate方法中调用,那么我们试试在onResume方法中调用呢?
只能说然并卵,打印的结果依然是:
06-26 20:52:13.986 19453-19453/uuch.com.Android_viewheight I/MainActivity: height:0 width:0
好吧,问题已经出来了,看样子我们是不能再onCreate方法或者是onResume方法中调用该方法获取组件的宽高的,但是这是为什么呢?平时我们都是通过这个方法来获取组件的宽高的,并且也没问题啊,比如我们将这个方法的调用逻辑写在按钮的点击事件之内呢?我们再来试试。
/**
* 这里的button1是我们定义的Button组件,并且我们重写了Button的点击事件,在其中调用了获取组件宽高的方法
*/
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getTextHeightAndWidth();
}
});
这样敲完代码之后,我们执行这段代码,界面入下图所示:
那么我们的打印结果呢?
06-26 20:57:08.188 22648-22648/uuch.com.Android_viewheight I/MainActivity: height:57 width:225
恩?这时候我们发现其打印出了组件的宽和高,那么为什么我们在Activity的onCreate、onResume方法中打印的时候,输出的组件宽高都是为0呢?
原因:
其实看过我以前写过的
Android源码解析之(十四)–>Activity启动流程
Android源码解析(十七)–>Activity布局加载流程
Android源码解析(十八)–>Activity布局绘制流程
的同学应该对Activity的启动流程和其布局加载绘制流程不陌生,Activity的启动流程和Activity的布局文件加载绘制流程,其实没有相关的关系的,其实两个异步的加载流程,这样我们在Activity的onCreate和onResume方法调用textView.getHeight或者是textView.getWidth方法的时候,其组件并没有执行完绘制流程,因此此时获取到的组件的宽高都是默认的0,也就是无法获取组件的宽和高。
但是当我们将获取组件宽高的方法卸载按钮的点击事件的时候,由于此时按钮已经显示出来了,所以证明布局文件已经加载绘制完成,这时候点击组件执行组件的获取宽高方法,就能正常的获取到组件的宽和高了。
这也就是为什么我们在onCreate和onResume方法中调用获取组件宽高都是0,而在按钮的点击事件中获取的时候正常的原因了。
其他解决方案:
那么如果我们想在Activity的onCreate方法或者是onReusme方法获取组件的宽高怎么办呢?这里提供了以下的五种方式:
- 重写Activity的onWindowFocusChanged方法
/**
* 重写Acitivty的onWindowFocusChanged方法
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
/**
* 当hasFocus为true的时候,说明Activity的Window对象已经获取焦点,进而Activity界面已经加载绘制完成
*/
if (hasFocus) {
int widht = titleText.getWidth();
int height = titleText.getHeight();
Log.i(TAG, "onWindowFocusChanged width:" + widht + " "
+ " height:" + height;
}
}
说明:
这样重写onWindowFocusChanged方法,当获取焦点的时候我们就可以通过getWidth和getHeight方法得到组件的宽和高了。但是这时候这个方法的逻辑可能会执行多次,也就是说只要我们的Activity的window对象获取了焦点就会执行该语句,所以我们需要做一些逻辑判断,让它在我们需要打印获取组件宽高的时候在执行。
- 为组件添加OnGlobalLayoutListener事件监听
/**
* 为Activity的布局文件添加OnGlobalLayoutListener事件监听,当回调到onGlobalLayout方法的时候我们通过getMeasureHeight和getMeasuredWidth方法可以获取到组件的宽和高
*/
private void initOnLayoutListener() {
final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Log.i(TAG, "开始执行onGlobalLayout().........");
int height = titleText.getMeasuredHeight();
int width = titleText.getMeasuredWidth();
Log.i(TAG, "height:" + height + " width:" + width);
// 移除GlobalLayoutListener监听
MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
说明:
需要说明的是这里的onGlobalLayout方法会在Activity的组件执行完onLayout方法之后执行,这里的onLayout方法主要用于计算组件的宽高操作,具体可参考:Android源码解析(十八)–>Activity布局绘制流程,这样当我们计算完组件的宽高之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。
- 为组件添加OnPreDrawListener事件监听
/**
* 初始化viewTreeObserver事件监听,重写OnPreDrawListener获取组件高度
*/
private void initOnPreDrawListener() {
final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
Log.i(TAG, "开始执行onPreDraw().........");
int height = titleText.getMeasuredHeight();
int width = titleText.getMeasuredWidth();
Log.i(TAG, "height:" + height + " width:" + width);
// 移除OnPreDrawListener事件监听
MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
}
说明:
需要说明的是这里的onPreDraw方法会在Activity的组件执行onDraw方法之前执行,熟悉我们Activity组件加载绘制流程的同学应该知道,这里的onDraw方法主要用于执行真正的绘制组件操作,而这时候我们已经计算出来了组件的位置,宽高等操作,这样之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。
- 使用View.post方法获取组件的宽高
/**
* 使用View的post方法获取组件的宽度和高度
*/
private void initViewHandler() {
titleText.post(new Runnable() {
@Override
public void run() {
int width = titleText.getWidth();
int height = titleText.getHeight();
Log.i(TAG, "initViewHandler height:" + height + " width:" + width);
}
});
}
说明:
这里的view的post方法底层使用的是Android的异步消息机制,消息的执行在MainActivity主进程Loop执行之后,所以这时候也可以获取组件的宽高,更多关于Android中异步消息的内容可参考我的:Android源码解析之(二)–>异步消息机制。
- 通过Handler对象使用异步消息获取组件的宽高
/**
* 在onCreate方法中发送异步消息,在handleMessage中获取组件的宽高
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 101) {
int width = titleText.getWidth();
int height = titleText.getHeight();
Log.i(TAG, "initViewHandler height:" + height + " width:" + width);
}
}
};
说明:
和上面使用view.post方法类似,这里用的是异步消息获取组件的宽高,而这里的异步消息的执行过程是在主进程的主线程的Activity绘制流程之后,所以这时候可以获取组件的宽高。
其他说明:
需要说明的是如果对Acitivty的布局加载绘制流程比较了解的同学应该知道,界面的显示过程经过了:测量位置,测量大小,绘制,三个操作流程。而我们获取组件的宽高就是获取组件的大小,所以我们获取的代码必须要在组件执行完测量大小之后,而无论是我们添加的onWindowFocusChanged方法,onPreDrawListener监听,已经onGlobalLayoutListener监听其实都是在组件完成了测量大小之后执行了,因此这时候我们能够正确的获取到组件的宽和高。
总结:
该类库主要是介绍了五种我们在Activity的启动过程中获取组件宽高的方式;
通过重写onWidnowFocusChanged方法获取组件宽高的方式,可能会回调几次,这点需要我们注意;
项目保存地址:Android-viewheight,欢迎star和follow
另外对github项目,开源项目解析感兴趣的同学可以参考我的:
Github项目解析(一)–>上传Android项目至github
Github项目解析(二)–>将Android项目发布至JCenter代码库
Github项目解析(三)–>Android内存泄露监测之leakcanary
Github项目解析(四)–>动态更改TextView的字体大小
Github项目解析(五)–>Android日志框架
Github项目解析(六)–>自定义实现ButterKnife框架
Github项目解析(七)–>防止按钮重复点击