Search code examples
androidelementtransitionshared

Shared transition between fragment1 inside activity1 and fragment2 inside activity2


Is there any way to make a shared transition between fragment1 inside activity1 and fragment2 inside activity2?

I have tried achieving this like so:

    val intent = Intent(this, RecipeActivity::class.java)
    intent.putExtra("recipeId", recipeId)
    val elem1 =
        Pair<View, String>(itemView.findViewById(R.id.recipe_preview), "preview")
    val elem2 =
        Pair<View, String>(itemView.findViewById(R.id.recipe_title), "title")
    val elem3 =
        Pair<View, String>(itemView.findViewById(R.id.recipe_rating_stars), "rating_stars")
    val elem4 =
        Pair<View, String>(itemView.findViewById(R.id.recipe_rating), "rating")
    val elem5 =
        Pair<View, String>(itemView.findViewById(R.id.recipe_description), "description")
    val elem6 =
        Pair<View, String>(itemView.findViewById(R.id.author_avatar), "avatar")
    val options =
        ActivityOptionsCompat.makeSceneTransitionAnimation(
            this, elem1, elem2, elem3, elem4, elem5, elem6
        )
    startActivity(intent, options.toBundle())

But that didn't work so well. Do I have to redesign my app so those two fragments will be inside a single activity or is there any workaround? Thank you


Solution

  • The idea: Pause the transaction until the targed fragment is fully loaded, created and is about to be drawn. Then continue the transaction.

    The code: Everything you do in your first activity is ok and we won't touch it. The first thing your activity has to is to stop the transaction. Therefore you need to call supportPostponeEnterTransition() in onCreate() of your second activity. This will tell android to wait with the transaction until you tell it to start it.

    Secondly you need to know when the fragment is about to be drawn. In my use case I display some fragments in a ViewPager what makes things a lot easier, as you can add an ViewTreeObserver to it and wait until the ViewPager is loaded because you know that at this point the fragments are already created and basically drawn even if you can't see them. When using frgaments the normal way with transaction you need some trickery.

    Important: Everything from now on is not tested but in theory it should work.

    In your fragment you do something like this:

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
    

    Instead you to do it like this:

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_home, container, false)
        return view
    }
    

    You need the root view of your fragment because we will add the ViewTreeObserver to it.

    But before you to that you need to create an interface in your FragmentClass or add the method to your preexisting one:

    interface FragmentListener {
       fun resumeTransaction()
    }
    

    You need to implement it in your activity:

    override fun resumeTransaction() {
        supportStartPostponedEnterTransition()
    }
    

    In your fragment we need to get the Activity as listener. In the onAttach do following:

    try {
        // Instantiate the FragmentListener so we can send the event to the host
        listener = context as FragmentInterface
    } catch (e: ClassCastException) {
        // The activity doesn't implement the interface, throw exception
        throw ClassCastException((context.toString() + " must implement FragmentInterface"))
    }
    

    Now we get back to the ViewTreeObserver. In the onCreateView you do this:

    viewPager.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                listener.resumeTransaction()
            }
        }
    )
    

    If I didn't forget anything this should work. If not please tell me, I will then try to make an example app later this day as I don't have more time now.