getMeasureWidth():测量宽度,onMeasure之后即可获得
getWidth():实际显示宽度,onLayout之后可获得
测量宽度和实际宽度,几乎所有情况相等,特殊情况除外如:多次测量只有最后一次相等,或重写onLayout方法了
getMeasureWidth调用时机
好习惯是在onLayout方法中获取测量的最终宽高
通过View的绘制流程,我们知道在View绘制完成之后就可以通过getMeasureWidth正确获得View的测量宽度。注意,某些极端情况下系统需要多次测量才能确定最终的宽高,这样onMeasure方法中取到的值很可能不正确。
Activity中如何获取宽高?
在onCreate或者onResume中获取getMeasureWidth是不可行的。
因为View的measure过程和生命周期方法不是同步执行的(Activity的启动流程和Activity的布局文件加载绘制流程,其实没有相关的关系的,其实两个异步的加载流程)。不能保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕,此时获取宽高就是0。
onCreate中getWidth为0的解决方案(getMeasureWidth与getWidth都适用)
1. 重写Activity/View的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对象获取了焦点就会执行该语句,所以我们需要做一些逻辑判断,让它在我们需要打印获取组件宽高的时候在执行。
2. 使用ViewTreeObserver 的众多回调方法
- 为组件添加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方法主要用于执行真正的绘制组件操作,而这时候我们已经计算出来了组件的位置,宽高等操作,这样之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。
3. 使用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源码解析之(二)–>异步消息机制。
4. 通过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绘制流程之后,所以这时候可以获取组件的宽高。
5. view.measure(int widthMeasureSpec,int heightMeasureSpec)
通过手动对View进行measure来得到View的宽高,但需要根据不同情况来进行处理
- match_parent
这个无法measure具体的宽高,是因为需要知道父容器的parentSize,但这个时候是无法获取的。 - 具体的数值
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
- 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);
原文:https://blog.csdn.net/qq_23547831/article/details/51764304