Shared element transition between fragments leaves mysterious offset

860 Views Asked by At

I have a simple app showing a gallery of images. Upon clicking one of them I want it to transition into a detail fragment. Clicking the detail fragment will pop the fragment backstack. The transition works but it always leaves an offset on the right on enter animation and an offset at the top on exit animation.

Initial

Initial

Enter

Detail

Exit

Exit

Transition set

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="100"
>
<changeBounds/>
<changeClipBounds/>
<changeTransform/>
<changeImageTransform/>
</transitionSet>

RecyclerView item XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<ProgressBar
    android:id="@+id/progress"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:visibility="gone"
    />

<ImageView
    android:id="@+id/image_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop"
    />

</FrameLayout>

Detail XML

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
    android:id="@+id/image_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitCenter"
    android:adjustViewBounds="true"
    />

</FrameLayout>

Open detail fragment logic

override fun openImageFragment(imageView: ImageView) {
    val fragment = ImageFragment.newInstance(imageView.transitionName)
    fragment.drawable = imageView.drawable

    supportFragmentManager
        .beginTransaction()
        .setReorderingAllowed(true)
        .addToBackStack(null)
        .addSharedElement(imageView, imageView.transitionName)
        .replace(R.id.container_fragment, fragment)
        .commit()
}

Detail fragment

class ImageFragment : Fragment() {

private lateinit var binding: FragmentImageBinding
var drawable: Drawable? = null

companion object {
    const val KEY_IMAGE = "IMAGE"

    fun newInstance(transitionName: String): ImageFragment {
        val bundle = Bundle().also {
            it.putString(KEY_IMAGE, transitionName)
        }
        val fragment = ImageFragment()
        fragment.arguments = bundle
        return fragment
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    postponeEnterTransition()
    val transition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.scale_image)
    transition.addListener(object : TransitionListenerAdapter() {
        override fun onTransitionEnd(transition: Transition) {
            binding.root.setOnClickListener {
                requireActivity().supportFragmentManager.popBackStack()
            }
        }
    })
    sharedElementEnterTransition = transition
    sharedElementReturnTransition = transition
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = FragmentImageBinding.inflate(inflater, container, false)
    binding.imageView.transitionName = arguments?.getString(KEY_IMAGE)
    binding.imageView.setImageDrawable(drawable)
    binding.imageView.doOnPreDraw { startPostponedEnterTransition() }

    return binding.root
}
}
2

There are 2 best solutions below

0
On

This seems to be a bug with fragment to fragment transition. I "solved" it by switching to an activity.

0
On

As you say @geft, it seems to be a bug between fragments, because I have taken the GridToPager Google example project and I have passed it to RecyclerView and it makes the fade effect but the item does not move, but with the ViewPager it does.

Activity working

Example Google not working with fragments, RecyclerView

I have created an issue in their Github to see if they can fix it, because in my case changing to activity that part is very complicated and I see it unnecessary when the fault is theirs.