能否使用MotionLayout或Transition实现Fragment间共享元素动画?
实现方案
1. 提前初始化Fragment B的ViewPager状态
切换到Fragment B前,必须确保其内部ViewPager已加载并切换到索引1的首页Fragment,否则动画会错位:
- 在Fragment B的
onViewCreated方法中强制ViewPager切换目标页并开启预加载:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val viewPager = view.findViewById<ViewPager2>(R.id.view_pager) // 直接切换到索引1的首页Fragment,无滑动动画 viewPager.setCurrentItem(1, false) // 预加载所有4个页面,确保首页Fragment已初始化完成 viewPager.offscreenPageLimit = 3 }
2. 基础版:固定中心缩放过渡动画
通过自定义动画资源,实现Fragment A全屏缩小退出、Fragment B配合放大进入的效果:
2.1 创建动画资源文件
在res/anim目录下创建scale_out.xml(Fragment A退出动画):
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="1.0" android:toXScale="0.9" android:fromYScale="1.0" android:toYScale="0.9" android:pivotX="50%" android:pivotY="50%" android:duration="500"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="500"/> </set>
创建scale_in.xml(Fragment B进入动画):
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="0.9" android:toXScale="1.0" android:fromYScale="0.9" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" android:duration="500"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="500"/> </set>
2.2 触发Fragment切换并应用动画
在Fragment A的关闭逻辑中(比如完成按钮点击事件),调用FragmentTransaction设置动画:
btnFinish.setOnClickListener { val fragmentB = FragmentB() parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.scale_in, R.anim.scale_out) .replace(R.id.container, fragmentB) .commit() }
3. 进阶版:精准缩放到首页Fragment位置
如果需要动画精准对齐首页Fragment的布局位置,可通过动态计算缩放原点实现:
3.1 在Fragment B中获取首页Fragment实例
fun getHomeFragment(): Fragment? { // ViewPager2的子Fragment标签格式为"f${position}" return childFragmentManager.findFragmentByTag("f1") }
3.2 动态计算并执行缩放动画
btnFinish.setOnClickListener { val fragmentB = FragmentB() // 先将Fragment B添加到后台并初始化,确保子Fragment已加载 parentFragmentManager.beginTransaction() .add(R.id.container, fragmentB, "fragment_b") .hide(fragmentB) .commitNow() fragmentB.getHomeFragment()?.view?.let { homeView -> // 获取首页Fragment视图在屏幕上的中心坐标 val location = IntArray(2) homeView.getLocationOnScreen(location) val centerX = location[0] + homeView.width / 2 val centerY = location[1] + homeView.height / 2 // 动态创建缩放动画 val scaleAnimator = ObjectAnimator.ofPropertyValuesHolder( requireView(), PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 0.9f), PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 0.9f), PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f) ) scaleAnimator.duration = 500 scaleAnimator.addListener(object : Animator.AnimatorListener { override fun onAnimationEnd(animation: Animator?) { // 动画结束后显示Fragment B并移除当前Fragment A parentFragmentManager.beginTransaction() .show(fragmentB) .remove(this@FragmentA) .commit() } override fun onAnimationStart(animation: Animator?) {} override fun onAnimationCancel(animation: Animator?) {} override fun onAnimationRepeat(animation: Animator?) {} }) // 设置缩放原点为首页Fragment的中心 requireView().pivotX = centerX.toFloat() requireView().pivotY = centerY.toFloat() scaleAnimator.start() } ?: run { // 首页Fragment未加载时, fallback到基础版动画 parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.scale_in, R.anim.scale_out) .replace(R.id.container, fragmentB) .remove(this@FragmentA) .commit() } }
4. 关键注意事项
- 调整动画中的缩放比例(如
0.9),使其与首页Fragment的实际尺寸匹配,确保视觉衔接自然。 - 若使用ViewPager而非ViewPager2,子Fragment的标签格式为
android:switcher:{containerId}:{position},需对应修改获取逻辑。 - 动态动画需确保Fragment B已完成视图初始化,否则无法获取首页Fragment的视图坐标。
内容的提问来源于stack exchange,提问作者Issane Ren
相关产品推荐
相关产品推荐

