Search code examples
androidandroid-recyclerviewshared-element-transitionandroid-architecture-navigation

NavigationComponent recyclerview return shared element transition


I use Navigation Component in project. Trying to set up shared element transitions from recycler. And enter transition in the second fragment works fine, but when I return to the first fragment - there is no return transition.

I tried to explicitly set enter and return transitions in the first fragment like I did in the second like this

val transition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)

sharedElementEnterTransition = transition

sharedElementReturnTransition = transition

but it didn't help.

Also tried to remove enter and exit fragment animations. For the second fragment enter transition works with animations, but who knows.

also tried to use the solution from this question, but in my case it didn't work. https://stackoverflow.com/a/52922835/10951565

mainFragment

stickyAdapter.onItemClick = { event, imageView, textView ->
            val args = bundleOf(EventDetailFragment.EVENT to event)
            val extras = FragmentNavigatorExtras(
                imageView to imageView.transitionName,
                textView to textView.transitionName
            )
            findNavController().navigate(R.id.action_global_eventDetailFragment, args, null, extras)
        }

in adapter onClick I set unique transition names for the views: mainFragmentAdapter

eventImageView.setOnClickListener {
            titleTextView.transitionName = "${event.title}$TRANSITION_TITLE"
            eventImageView.transitionName = "${event.title}$TRANSITION_IMAGE" 
            onItemClick?.invoke(event, eventImageView, titleTextView) 
        }

in detailsFragment I get transition names from arguments (copypasted them to be sure there is no mistakes and for enter transition it worked) I call postponeEnterTransition() in OnCreate method to wait until i can set transition names, sharedElementEnterTransition and sharedElementReturnTransition in onViewCreated and then call startPostponedEnterTransition()


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

override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.fragment_event_detail, container, false).apply {
        val event = arguments?.getSerializable(EVENT) as Event?
        toolbarImageView.transitionName = "${event?.title}$TRANSITION_IMAGE"
        titleTextView.transitionName = "${event?.title}$TRANSITION_TITLE"

        val transition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
        sharedElementEnterTransition = transition
        sharedElementReturnTransition = transition
        startPostponedEnterTransition()

        toolbar.setupWithNavController(findNavController())

        event?.let {
            Picasso.get()
                .load("${EventGroupAdapter.BASE_SMALL_IMAGE_URL}${event.smallimage}")
                .into(toolbarImageView)
        }
    }
}

main_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".view.activity.MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:background="?android:colorBackground"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true">

                <androidx.viewpager.widget.ViewPager
                    android:id="@+id/viewPager"
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:fitsSystemWindows="true" />

                <com.rd.PageIndicatorView
                    android:id="@+id/pageIndicatorView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignBottom="@id/viewPager"
                    android:layout_centerHorizontal="true"
                    android:layout_marginBottom="7dp"
                    app:piv_dynamicCount="true"
                    app:piv_interactiveAnimation="true"
                    app:piv_radius="3.5dp"
                    app:piv_selectedColor="@color/colorIndicatorSelected"
                    app:piv_unselectedColor="@color/colorIndicatorUnselected"
                    app:piv_viewPager="@id/viewPager" />

                <include
                    android:id="@+id/horizontalScrollView"
                    layout="@layout/fragment_main_horizontal_scroll_view"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/viewPager" />

            </RelativeLayout>

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

        <RelativeLayout
            android:id="@+id/relativeLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <LinearLayout
                android:id="@+id/emptyMessage"
                android:layout_marginTop="40dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layoutAnimation="@anim/layout_animation_from_bottom"
                android:orientation="vertical">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:src="@drawable/ic_empty_events"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="@string/empty_events_message"
                    android:textSize="18sp" />
            </LinearLayout>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:layout_marginBottom="48dp"
                android:nestedScrollingEnabled="true"

                tools:listitem="@layout/fragment_main_event_item" />

        </RelativeLayout>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/programTextView"
        android:layout_gravity="center"
        android:visibility="gone"
        tools:visibility="visible" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:colorBackground"
        app:menu="@menu/bottom_navigation" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Please share your recipe of the return shared element transition with Navigation Component


Solution

  • I ran into this the other day where going from the MainFragment item to a DetailFragment animated fine, but the sharedElementReturnTransition wasn't working when pressing back.

    The fix for me was to postpone the transition on the MainFragment. At first, I was trying to do so in onCreate, but onCreate isn't always called when going back, so the fix was to postpone it in onViewCreated (I would guess onCreateView might work too) and then start the transition once the RecyclerView is ready.

    DetailFragment:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val transition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
        sharedElementEnterTransition = transition
        sharedElementReturnTransition = transition
    }
    

    MainFragment:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        postponeEnterTransition()
        recycler_view.post { startPostponedEnterTransition() }
    }