View.post与Runnable.run的差异解析:Compound View中onSizeChanged修改子View可见性随机失效问题原因探究
咱们先拆解这个问题的核心:这本质是View测量布局流程的时机匹配问题,结合ConstraintLayout的特殊布局机制就很好理解了。
一、直接在onSizeChanged()修改失效的原因
onSizeChanged()的回调时机是当前View的测量阶段完成后——也就是自身宽高已经确定,但这时候整个View树的布局阶段(layout)可能还没完全走完,尤其是ConstraintLayout这种依赖约束计算的布局容器,它内部的子View约束解析、位置计算可能还在进行中。
当你在这个时机直接修改子View的可见性时,会出现两个关键问题:
- ConstraintLayout此时可能还在处理自身的尺寸变化逻辑,没有及时监听子View的可见性变更,导致没有触发约束的重新计算(比如goneMargin、约束链的调整)。
- 后续的布局流程可能会覆盖你的修改:因为ConstraintLayout在布局阶段会根据预设约束重新确定子View状态,你的修改如果赶在布局完成前执行,可能被内部的布局逻辑冲掉。
问题的随机性来自不同场景下View树测量布局的耗时差异:有时候布局刚好在你修改前完成,修改就生效;有时候布局还在进行,修改就被覆盖,所以表现出随机失效的现象。
二、post{}和doOnPreDraw{}为什么能解决问题?
这两个方法都是把你的修改操作放到了View树状态稳定的时机执行,让ConstraintLayout能正确响应可见性变化:
1. post{}的作用
post{}会把你的任务放到UI线程的消息队列尾部,等当前所有的测量、布局、绘制任务都执行完成后,再执行修改操作。
此时整个View树已经完全稳定,修改子View可见性会直接触发ConstraintLayout的重新布局请求,约束计算和状态更新都会正常执行,修改自然能正确生效。
2. doOnPreDraw{}的作用
doOnPreDraw{}是在View树即将开始绘制前的回调,这时候测量和布局阶段已经100%完成,所有子View的尺寸、位置都已经确定。
在这个时机修改可见性,会立即触发一次新的布局请求(因为可见性变化会影响布局结构),然后再进入绘制流程——相当于在当前绘制周期内就完成了修改,不会等到下一个消息循环,时机比post{}更早。
三、两种实现方式的差异
| 特性 | post{} | doOnPreDraw{} |
|---|---|---|
| 执行时机 | 当前UI任务(测量/布局/绘制)全部完成后,下一次消息循环 | 绘制前的最后一步,当前绘制周期内 |
| 布局触发时机 | 触发下一次布局+绘制 | 当前绘制周期内触发重新布局+绘制 |
| 适用场景 | 不需要立即生效的UI修改 | 需要在当前绘制周期内生效的修改 |
简单来说,doOnPreDraw{}是“赶在这次绘制前搞定”,post{}是“等这次所有操作做完再搞定”,两者都避开了布局未完成的不稳定阶段,所以都能解决你的问题。
内容的提问来源于stack exchange,提问作者Sergei Buvaka

