AppBarLayout下Fragment共享元素过渡Z轴层级异常问题
你的问题核心原因很明确:AppBarLayout在滚动偏移时,会动态调整子元素的Z轴高度(elevation),这直接干扰了Shared Element Transition的绘制层级逻辑。
为什么会出现这个问题?
AppBarLayout的动态elevation机制
AppBarLayout为了实现折叠时的悬浮阴影效果,会在滚动过程中根据偏移量自动调整自身和内部视图(比如CollapsingToolbarLayout里的ImageView、Scrim)的elevation。当AppBar处于部分折叠状态时,这些内部视图的elevation会被提升,导致它们的Z轴高度超过了下方的contentList,动画时自然就会跑到上面。Shared Element Transition的层级依赖
过渡动画执行时,系统是基于视图当前的Z轴值来确定绘制顺序的。当AppBar有偏移时,image和scrim的elevation已经被AppBarLayout修改,不再是展开状态下的初始层级关系,所以动画过程中就出现了Z轴层级反转的问题。
解决方案
这里有几个靠谱的解决思路,你可以根据自己的需求选择:
1. 强制锁定过渡视图的Z轴值
在启动过渡动画前后,手动设置相关视图的elevation,确保它们的层级关系在整个动画过程中保持一致。比如在Fragment A准备跳转时:
// Fragment A中启动跳转前的代码 binding.image.elevation = 0f binding.scrim.elevation = 0f binding.contentList.elevation = 1f // 让contentList保持在最上层
同时在Fragment B的onViewCreated中也设置对应的视图elevation,保持两端的层级统一。
2. 禁用AppBarLayout的自动elevation调整
如果你的场景不需要AppBar折叠时的悬浮阴影效果,可以直接在XML里关闭这个特性:
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="wrap_content" app:elevation="0dp" app:liftOnScroll="false">
liftOnScroll="false"会阻止AppBar在滚动时自动提升elevation,这样内部视图的Z轴值就不会被动态修改了。
3. 通过TransitionListener手动控制层级
在过渡动画的生命周期中,监听动画开始事件,强制修正视图层级:
val sharedTransition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.your_shared_element_transition) sharedTransition.addListener(object : Transition.TransitionListener { override fun onTransitionStart(transition: Transition) { // 动画启动时强制设置层级 binding.image.elevation = 0f binding.scrim.elevation = 0f binding.contentList.elevation = 1f } override fun onTransitionEnd(transition: Transition) {} override fun onTransitionCancel(transition: Transition) {} override fun onTransitionPause(transition: Transition) {} override fun onTransitionResume(transition: Transition) {} }) sharedElementEnterTransition = sharedTransition
总结
问题的本质就是AppBarLayout的动态elevation调整打破了Shared Element Transition依赖的初始层级关系,只要通过上述方法锁定或修正视图的Z轴高度,就能解决动画过程中层级反转的问题。
内容的提问来源于stack exchange,提问作者gookman

