View.post() 会闪一帧错误状态,跳变闪烁问题解决

一、为什么 View.post() 会闪一帧错误状态

执行时序

  1. View 初始化 → 先渲染默认错误 UI(空白 / 加载态 / 旧数据)并提交绘制
  2. View.post() 将任务抛到主线程消息队列末尾
  3. 主线程先完成当前帧完整绘制(用户看到错误画面),下一帧才执行 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更新逻辑
}

三、关键注意点(极易踩坑)

  1. 必须手动 removeOnPreDrawListener onPreDraw 每一次视图重绘都会触发,不删除会无限回调,造成页面卡顿、重复执行逻辑。
  2. 监听时机 要在 View 被 attach 到 Window 前注册监听(onCreate/onViewCreated);如果 View 还未加载,viewTreeObserver 有效。
  3. 返回值含义
    • return true:正常执行本次绘制(推荐)
    • return false:取消本次绘制,会重新触发一次 onPreDraw,慎用
  4. 对比 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

转自:豆包 - 字节跳动旗下 AI 智能助手

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值