You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Fragment共享元素转场前应用Window Inset避免视图跳动?

解决Shared Element Transition前Window Inset导致的视图跳动问题

我完全理解你遇到的困扰——共享元素转场已经实现了Logo从居中到顶部的平滑移动,但Window Inset的延迟生效让转场结束后Logo突然跳动,直接破坏了过渡的流畅感。本质问题在于你在onActivityCreated中设置Insets监听的时机,晚于共享元素转场的初始化流程,导致转场启动时视图还没被Insets调整到最终位置。

下面给你几个可行的解决方案:

方案一:提前在onCreateView阶段处理Insets并确保布局生效

把Insets的处理逻辑移到onCreateView中,同时利用ViewTreeObserver等待视图完成布局和Insets应用后,再让转场正常执行。这样转场启动时,Logo已经处于Insets调整后的位置,就不会出现跳动了。

修改后的代码示例:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.onboarding_layout, container, false)
    val logo = view.findViewById<ImageView>(R.id.appimageviewLogo)
    val skipButton = view.findViewById<Button>(R.id.appbuttonSkip)

    // 提前设置Insets监听
    ViewCompat.setOnApplyWindowInsetsListener(logo) { view, insets ->
        val params = view.layoutParams as ViewGroup.MarginLayoutParams
        params.topMargin += insets.systemWindowInsetTop
        view.layoutParams = params // 必须重新赋值LayoutParams才会触发布局更新
        insets
    }

    ViewCompat.setOnApplyWindowInsetsListener(skipButton) { view, insets ->
        val params = view.layoutParams as ViewGroup.MarginLayoutParams
        params.bottomMargin += insets.systemWindowInsetBottom
        view.layoutParams = params
        insets.consumeSystemWindowInsets()
    }

    // 等待视图布局完成,确保Insets已应用
    view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            // 移除监听避免重复触发
            view.viewTreeObserver.removeOnGlobalLayoutListener(this)
            // 手动请求应用Insets
            ViewCompat.requestApplyInsets(logo)
            ViewCompat.requestApplyInsets(skipButton)
            // 若转场是自动启动的,可在这里触发相关初始化逻辑
        }
    })

    return view
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    // 原Insets处理代码可移除
}

方案二:给共享元素转场添加监听,延迟到Insets应用后执行

如果不想改动onCreateView的逻辑,可以给共享元素转场设置TransitionListener,在转场开始前等待Insets应用完成:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    val logo = appimageviewLogo
    val skipButton = appbuttonSkip
    var insetsApplied = false

    // 设置Insets监听,标记Insets已应用
    ViewCompat.setOnApplyWindowInsetsListener(logo) { view, insets ->
        val params = view.layoutParams as ViewGroup.MarginLayoutParams
        params.topMargin += insets.systemWindowInsetTop
        view.layoutParams = params
        insetsApplied = true
        insets
    }

    ViewCompat.setOnApplyWindowInsetsListener(skipButton) { view, insets ->
        val params = view.layoutParams as ViewGroup.MarginLayoutParams
        params.bottomMargin += insets.systemWindowInsetBottom
        view.layoutParams = params
        insets.consumeSystemWindowInsets()
    }

    ViewCompat.requestApplyInsets(logo)
    ViewCompat.requestApplyInsets(skipButton)

    // 给共享元素转场添加监听
    requireActivity().window.sharedElementEnterTransition?.addListener(object : Transition.TransitionListener {
        override fun onTransitionStart(transition: Transition) {
            // 若Insets未应用,暂停转场
            if (!insetsApplied) {
                transition.pause()
                // 等待布局完成后恢复转场
                logo.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        logo.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        transition.resume()
                    }
                })
            }
        }

        override fun onTransitionEnd(transition: Transition) {
            transition.removeListener(this)
        }

        override fun onTransitionCancel(transition: Transition) {
            transition.removeListener(this)
        }

        override fun onTransitionPause(transition: Transition) {}
        override fun onTransitionResume(transition: Transition) {}
    })
}

额外提示

  • 修改LayoutParams后,必须重新赋值给view.layoutParams,否则系统不会触发视图布局更新。
  • 如果你使用的是Android 11及以上版本,可以尝试使用WindowInsetsAnimationCompat实现Insets与转场的协同动画,进一步优化流畅度。

内容的提问来源于stack exchange,提问作者Archie G. Quiñones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:50:45