如何在Navigation Controller中使用共享元素转场
嘿,我刚好在项目里折腾过这个,其实Navigation组件完全支持共享元素转场,只是官方文档的细节有点散,我把完整的实现步骤拆解给你:
1. 给共享View设置统一的transitionName
首先要保证源Fragment和目标Fragment里的共享View(比如图片、文本框)拥有完全相同的transitionName属性(大小写都要一致!)。
举个例子,源Fragment的布局里:
<ImageView android:id="@+id/item_photo" android:transitionName="shared_photo" android:layout_width="100dp" android:layout_height="100dp" />
目标Fragment的对应View也要设置一样的:
<ImageView android:id="@+id/detail_photo" android:transitionName="shared_photo" android:layout_width="match_parent" android:layout_height="300dp" />
如果你是用RecyclerView列表跳转,每个item的共享View需要唯一的transitionName(比如
"shared_photo_${item.id}"),然后把这个名称通过bundle传到目标Fragment,在目标Fragment里动态设置对应View的transitionName,不然多个item转场会混乱。
2. 在导航图里配置转场动作
打开你的nav_graph.xml,找到跳转对应的<action>标签,添加共享元素转场的相关配置:
<action android:id="@+id/action_list_to_detail" app:destination="@id/detailFragment" <!-- 配置进入和返回的转场动画,这里用系统默认的缩放/位移转场 --> app:sharedElementsEnterTransition="@android:transition/changeBounds" app:sharedElementsReturnTransition="@android:transition/changeBounds" />
如果需要自定义转场效果,你可以自己创建transition资源文件(比如在res/transition目录下),然后替换这里的系统转场。
3. 触发导航时传递共享元素
这一步是核心,不能直接调用navigate(),要创建FragmentNavigator.Extras对象把共享View传递过去:
Kotlin代码示例:
// 假设是RecyclerView的item点击事件 holder.itemView.item_photo.setOnClickListener { val sharedView = it as ImageView // 构建Extras,绑定View和它的transitionName val extras = FragmentNavigator.Extras.Builder() .addSharedElement(sharedView, sharedView.transitionName) .build() // 带着Extras跳转,同时可以传递参数bundle findNavController().navigate( R.id.action_list_to_detail, bundleOf("photo_id" to item.id), // 可选参数 null, // NavOptions,比如动画配置 extras ) }
Java代码示例:
holder.itemView.findViewById(R.id.item_photo).setOnClickListener(v -> { ImageView sharedView = (ImageView) v; FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder() .addSharedElement(sharedView, sharedView.getTransitionName()) .build(); getNavController().navigate( R.id.action_list_to_detail, BundleHelper.createPhotoBundle(item.getId()), // 自定义参数bundle null, extras ); });
4. 优化转场体验(可选但推荐)
如果共享View是图片(尤其是网络图片),建议等图片加载完成后再开始转场,避免出现图片还没显示就转场的尴尬:
在目标Fragment的onViewCreated里:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 先延迟转场 postponeEnterTransition() val detailPhoto = view.findViewById<ImageView>(R.id.detail_photo) // 用Glide加载图片的话,在加载完成后触发转场 Glide.with(this) .load(getPhotoUrl()) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { startPostponedEnterTransition() return false } }) .into(detailPhoto) }
常见坑点排查
- 确保两个View的
transitionName完全一致,包括大小写和特殊字符 - 必须用Navigation组件的
navigate()方法跳转,不能手动replaceFragment - RecyclerView场景下一定要给每个item的共享View设置唯一的transitionName
- 如果转场没生效,检查导航图的
<action>里是否正确配置了共享元素转场属性
这样一套下来,就能实现流畅的共享元素转场效果了,要是还有细节问题可以随时问我!
内容的提问来源于stack exchange,提问作者JPLauber

