获得 View 宽高的几种方法

Activity / Fragment 的生命周期和 View 绘制过程的各个阶段是相互独立的,所以我们在 onCreate() 里面使用 getWidth()/getMeasuredWidth() 往往得到 0。

得到 View 宽高的正确方式有如下几种:

  1. ViewTreeObserver.OnPreDrawListener;
  2. ViewTreeObserver.OnGlobalLayoutChangeListener;
  3. View.measure(int widthMeasureSpec, int heightMeasureSpec, );
  4. 自定义控件;

前两种方法都是采用回调的方式,优点是简单、百分百正确,缺点是某些场景下局限较多、作用有限。

第三种方法的原理是模拟 View 的 measure 过程,执行完 measure 过程后再调用 View.getMeasuredWidth() / View.getMeasuredHeight() 得到的即为所求,但是局限如下:

  • 只适用于一次完成 measure 过程的 View,而无法完全适用于一次 measure 过程需要多次调用 measure() 方法的 View (如 RelativeLayout、TextView 以及使用 weight 属性的 LinearLayout 等);
  • 需要使用 MeasureSpec.makeMeasureSpec(int size, int mode)拼接出 measure() 方法的参数;

对于更复杂的场景,建议使用第四种方法——自定义控件(甚至可以直接继承 ViewGroup),在 onMeasure() 方法里面获得子 View 的宽高。

此外,坊间流传的另一种方法:

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

这种方法确实有效,但是却很侥幸。
之所以有效,是因为主线程开始执行 ViewRootImpl.scheduleTraversals() 方法的时候,会在主线程的消息队列的头部插入一个 Message,该 Message 的 target 为 null,asynchronous 标志位为 false 的同步消息,称为同步屏障(以下代码出自 API 23):

mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

当检测到该屏障时,系统就会跳过所有的同步消息,找到第一个异步消息,也就是执行 ViewRootImpl.scheduleTraversals() 消息,先执行该消息,以保证正常绘制帧、避免出现卡顿。

当 traversal 过程完成了,measure + layout + draw 过程都完成了,实际的宽高也都计算完毕。这个时候再执行 post 出去的消息,就能得到正确的宽高值。

具体原理见 【Android源码解析】View.post()到底干了啥

这是一个很鸡贼的方法,不是系统预设的方式,所以不建议使用该方法

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值