如何在Fragment共享元素转场前应用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
相关产品推荐
相关产品推荐

