ImageView共享元素转场前缩放问题及跨Fragment转场实现咨询
解决嵌套ViewPager+RecyclerView场景下共享元素转场的缩放问题
我碰到过好几个类似嵌套ViewPager+RecyclerView的共享元素转场问题,你遇到的转场前ImageView被缩放的情况,大概率是视图复用或者ViewPager预加载搞的鬼,下面给你几个针对性的解决思路,按优先级来试:
1. 先解决RecyclerView的视图复用问题
RecyclerView的ViewHolder复用机制是重灾区——当你点击ImageView后,如果用户快速滚动RecyclerView,或者转场启动有延迟,这个ImageView可能已经被滚出屏幕、被复用给其他列表项了,系统获取到的共享元素状态已经不是你点击时的样子,自然会出现异常缩放。
解决方法是用临时ImageView替代原视图参与转场:
- 点击时立即捕获原ImageView的Bitmap和屏幕位置
- 在Activity根布局创建一个和原视图完全重合的临时ImageView,用捕获的Bitmap填充它
- 把这个临时视图作为共享元素启动转场,转场结束后移除它
代码示例(Kotlin):
imageView.setOnClickListener { // 1. 捕获当前ImageView的状态 val bitmap = (imageView.drawable as BitmapDrawable).bitmap val location = IntArray(2) imageView.getLocationOnScreen(location) val transitionName = imageView.transitionName // 2. 创建临时ImageView,完全复刻原视图的位置和内容 val tempImageView = ImageView(requireContext()).apply { setImageBitmap(bitmap) layoutParams = FrameLayout.LayoutParams(imageView.width, imageView.height).apply { leftMargin = location[0] topMargin = location[1] } this.transitionName = transitionName } // 3. 添加到Activity根布局 val rootView = requireActivity().findViewById<FrameLayout>(android.R.id.content) rootView.addView(tempImageView) // 4. 启动转场逻辑(这里用FragmentNavigatorExtras传递共享元素) val extras = FragmentNavigatorExtras(tempImageView to "target_photo_view_transition") // 跳转到SlideshowFragment的子FragmentB的逻辑... // 5. 转场结束后清理临时视图 tempImageView.doOnPreDraw { if (it.isAttachedToWindow) { rootView.removeView(tempImageView) } } }
2. 调整ViewPager的预加载行为
默认ViewPager会预加载左右各一个Fragment,这会导致SlideshowFragment里的FragmentB提前初始化,PhotoView的尺寸提前确定。如果源ImageView和目标PhotoView的宽高比、尺寸差异较大,系统会强制缩放源视图来适配目标,就会出现你看到的转场前缩放问题。
解决方法有两个:
方案A:禁用预加载(简单但可能影响滑动流畅度)
// 在设置ViewPager的Adapter后调用 viewPager.offscreenPageLimit = 0
方案B:延迟初始化目标PhotoView
在FragmentB中,不要在onCreateView里立即设置PhotoView的图片和尺寸,等到转场开始时再匹配源视图的状态:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_b, container, false) val photoView = view.findViewById<PhotoView>(R.id.photo_view) // 监听共享元素转场开始事件,再设置目标视图状态 sharedElementEnterTransition.addListener(object : Transition.TransitionListener { override fun onTransitionStart(transition: Transition) { // 从MainFragment传递过来的源视图状态(可以通过Bundle传递宽高、Bitmap) val sourceWidth = arguments?.getInt("source_width") ?: 0 val sourceHeight = arguments?.getInt("source_height") ?: 0 val sourceBitmap = arguments?.getParcelable<Bitmap>("source_bitmap") photoView.setImageBitmap(sourceBitmap) photoView.layoutParams.apply { width = sourceWidth height = sourceHeight } photoView.requestLayout() } override fun onTransitionEnd(transition: Transition) {} override fun onTransitionCancel(transition: Transition) {} override fun onTransitionPause(transition: Transition) {} override fun onTransitionResume(transition: Transition) {} }) return view }
3. 自定义共享元素转场动画
如果上面两个方法都没解决,可能是系统默认的ChangeBounds转场逻辑强制缩放了源视图。你可以自定义转场动画,手动控制缩放行为:
// 在启动转场的地方设置自定义共享元素转场 val customTransition = ChangeBounds().apply { duration = 300 interpolator = DecelerateInterpolator() // 禁用自动缩放,保持源视图尺寸直到转场结束 resizeMode = ChangeBounds.RESIZE_MODE_NONE } supportFragmentManager.beginTransaction() .replace(R.id.main_container, slideshowFragment) .addSharedElement(tempImageView, "target_photo_view_transition") .setReorderingAllowed(true) .setSharedElementEnterTransition(customTransition) .commit()
内容的提问来源于stack exchange,提问作者tingyik90
相关产品推荐
相关产品推荐

