onCreate()、onResume() 中可以获取View的宽高吗?怎么做? View.post{} 为什么可以获取?

回答: View的宽高是在onLayout阶段才能最终确定的,而在Activity#onCreate中并不能保证View已经执行到了onLayout方法,也就是说Activity的声明周期与View的绘制流程并不是一一绑定。所以onCreate() 和 onResume() 中获取不到View的宽高值。以Handler为基础,View.post() 将传入任务的执行时机调整到View 绘制完成之后。

代码验证:

public class MyActivity extends Activity {
 
private static final String TAG = MyActivity.class.getSimpleName();
private Button mButton;
 
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
mButton = findViewById(R.id.my_button);
// 打印输出日志 1
Log.e(TAG, "打印输出日志 1---> onCreate() 获取View的测量宽度:" + mButton.getWidth());
// 打印输出日志 2
mButton.post(new Runnable() {
@Override
public void run() {
Log.e(TAG, "打印输出日志 2---> onCreate() 中 通过post方法获取View的测量宽度:" + mButton.getWidth());
}
});
}
 
@Override
protected void onResume() {
super.onResume();
// 打印输出日志 3
Log.e(TAG, "打印输出日志 3---> onResume() 获取View的测量宽度:" + mButton.getWidth());
}
}

**运行结果:只有通过post方法才能获取View的测量宽度 **

 

原因:View 的测绘绘制流程就是从 ViewRootImpl#performTraversals() 开始的,而这个方法的调用是在 onResume() 方法之后,所以在 onCreate() 和 onResume() 方法中拿不到 View 的测量值。

View.post{} 为什么可以获取?

通过post可以将一个Runnable投放到消息队列尾部,意思是将任务添加到消息队列中,保证在UI线程执行。从本质上说,它还是依赖于以Handler、Looper、MessageQueue、Message为基础的异步消息处理机制。相对于新建Handler进行处理更加便捷。

下面举一个常用的例子,比如在onCreate方法中获取某个view的宽高,而直接View#getWidth获取到的值是0。要知道View显示到界面上需要经历onMeasure、onLayout和onDraw三个过程,而View的宽高是在onLayout阶段才能最终确定的,而在Activity#onCreate中并不能保证View已经执行到了onLayout方法,也就是说Activity的声明周期与View的绘制流程并不是一一绑定。

那为什么调用post方法就能起作用呢?首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,而我们的任务通过View#post方法被添加到队列尾部,保证了在layout结束以后才执行

view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
int height = view.getHeight();
}
});

view.post(new Runnable() {} 这个runnable的执行时机具体是什么?不同版本之间有差异么?

在Android 7.0之前,view.post()中的runnbale 并不能确定被执执行。具体来说:

  1. View.post会分情况处理,如果View没有attachedToWindow时,会将Runnable对象缓存起来,如果View已经attachedToWindow,则会将Runnable对象post到主线程执行。
  2. 如果View.post的runnable对象被缓存起来,而在主线程中执行了post方法, performTraversals方法在主线程执行时,getRunQueue取出来的缓存对象即是主线程的runnable对象,可以正常执行;
  3. 如果View.post的runnable对象被缓存起来,而在子线程中执行post方法,则会在sRunQueues对应的子线程的数据结构中设置runnable对象,而performTraversals方法会在主线程执行,取出来的是sRunQueues对应主线程的数据结构,根据ThreadLocal执行机制,这个时候主线程取出来的值是空的,因此不执行。


  在Android 7.0之后,view.post()中的runnbale 能确定被执执行。具体来说:

      Android 7.0之后,除了performTraversal中会调用外,在View的dispatchAttachedToWindow中也会调用,但Android 7.0之后不管在主线程还是在子线程都可以成功执行view.post内部逻辑,并不是因为增加了调用时机,而是取消了ThreadLocal机制,使得不管在主线程还是子线程调用view.post方法,都会将runnable对象丢到主线程的任务队列中,更新UI或者获取view的信息。

参考文档:

  1. https://blog.csdn.net/u010347226/article/details/106964870
  2. https://blog.csdn.net/longlong2015/article/details/88826269
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值