一、为什么 View.post() 会闪一帧错误状态
执行时序
- View 初始化 → 先渲染默认错误 UI(空白 / 加载态 / 旧数据)并提交绘制
View.post()将任务抛到主线程消息队列末尾- 主线程先完成当前帧完整绘制(用户看到错误画面),下一帧才执行 post 回调更新 UI 现象:肉眼可见一帧闪烁、状态跳变。
ViewTreeObserver.OnPreDrawListener 时序(无闪烁核心)
onPreDraw 在视图即将绘制、但画布还没提交到屏幕前回调,此时修改控件属性:
- 不会先渲染错误状态,直接用最新数据完成首次绘制
- 真正做到首帧渲染就是正确 UI,消除跳变
二、标准无闪烁实现代码(Kotlin / Java)
Kotlin 版本
kotlin
// 目标View,如RecyclerView、TextView、布局容器
val targetView: View = findViewById(R.id.content_view)
val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// 1. 必须先移除监听,防止重复回调(无限循环绘制)
targetView.viewTreeObserver.removeOnPreDrawListener(this)
// 2. 在这里设置正确状态,首帧绘制直接生效
updateCorrectUI()
// 返回true:允许本次绘制正常执行
return true
}
}
// 绑定监听
targetView.viewTreeObserver.addOnPreDrawListener(preDrawListener)
fun updateCorrectUI() {
// 业务逻辑:赋值数据、切换控件可见性、修改文字、加载图片等
// 例:
// tvTip.text = "正确内容"
// progressBar.visibility = View.GONE
// contentLayout.visibility = View.VISIBLE
}
Java 版本
java
运行
View targetView = findViewById(R.id.content_view);
ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// 移除监听,避免重复触发
targetView.getViewTreeObserver().removeOnPreDrawListener(this);
// 更新正确UI状态
updateCorrectUI();
return true;
}
};
targetView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
private void updateCorrectUI() {
// 业务UI更新逻辑
}
三、关键注意点(极易踩坑)
- 必须手动 removeOnPreDrawListener
onPreDraw每一次视图重绘都会触发,不删除会无限回调,造成页面卡顿、重复执行逻辑。 - 监听时机 要在 View 被 attach 到 Window 前注册监听(
onCreate/onViewCreated);如果 View 还未加载,viewTreeObserver有效。 - 返回值含义
return true:正常执行本次绘制(推荐)return false:取消本次绘制,会重新触发一次onPreDraw,慎用
- 对比 View.post 时序总结
表格
方案 绘制时机 闪烁问题 适用场景 View.post 当前帧渲染完成后,下一帧执行 会出现一帧错误画面 不介意轻微闪烁、延迟执行操作 OnPreDrawListener 首帧绘制前拦截修改 UI 完全无跳变、无闪烁 初始化要立刻展示正确状态、防闪需求
四、拓展:封装通用防闪工具方法(Kotlin)
kotlin
fun View.runBeforeFirstDraw(block: () -> Unit) {
viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
block.invoke()
return true
}
})
}
// 使用方式
contentView.runBeforeFirstDraw {
// 首帧渲染前更新UI,无闪烁
initData()
}
五、特殊场景兜底
如果 View 存在延迟加载(如 Fragment 懒加载、ViewPager 预加载),可增加 view.isShown 判断或搭配 OnGlobalLayoutListener,但防闪首帧渲染优先用 OnPreDrawListener。

487

被折叠的 条评论
为什么被折叠?



