Search code examples
android-recyclerviewshared-element-transition

Why is this Shared Element view not being automatically removed after the shared element transition ends?


I have 2 RecyclerViews - Overview & Detail RV. When a user clicks on an item in Overview RV, the clicked View is added as a Shared Element in the Fragment Transaction and transitions to Detail RV.

Problem:

The motion part of the transition works very well. However, after the transition finishes, the shared element View is not removed from Detail RV! When I scroll Detail RV, I can see 2 instances of the shared element! One scrolling with the rest of the content (which is what I want), and the other stuck at a fixed position on the screen. Only when I scroll the shared element out & back into the RV's viewport, forcing a rebind, will the unwanted shared element disappear.

What is causing the shared element View not being removed automatically?

Code:

This is how I add the clicked View as a target for shared element transitions.

// OverviewAdapter.kt:

view.setOnClickListener { view ->
    val sharedElements = getAllChildrenWithTransitionNames(view) // returns Map<String,View>

    supportFragmentManager.beginTransaction()
        .replace(R.id.fragmentContainer, DetailFragment())
        .addToBackStack(null)
        .also{ ft ->
            ft.setReorderingAllowed(true)
            sharedElements.forEach { (tn, view) ->
                ft.addSharedElement(view, tn)
            }
        }
        .commit()
}

This is how I prepare the shared element transition in DetailFragment. The clicked item will always be the first unique item in DetailFragment, so it is guaranteed to be drawn.

// DetailFragment.kt
class DetailsTransition : TransitionSet() {
    init {
        addTransition(ChangeBounds())
        addTransition(ChangeTransform())
        addTransition(ChangeImageTransform())
    }
}

val listAnimator = MyRVItemAnimator()

override fun onCreate(saved: Bundle?){
    listAnimator.blockAnimations = true // Detail RV has an item animator. This blocks animations during transitions to prevent flicker
    sharedElementEnterTransition = DetailsTransition().also {
        it.addListener(
          onEnd = {
            listAnimator.blockAnimations = false
          }
        )
}


// DetailAdapter.kt
override fun onBindViewHolder(holder: VH, position: Int) {
    detailFragment.startPostponedEnterTransition()
}

I've also made sure all transitionNames are unique and matching. So why isn't the shared element View being removed automatically after the transition finishes?


Solution

  • Turns out my RV ItemAnimator had a bug in it. When I was blocking animations, I did not call dispatchAdd/RemoveFinished(holder). This caused the shared element View to remain behind.