androidandroid-fragmentschild-fragment

Shared Element Transition Not Working Between Parent and Child Fragments (Nested Fragments)


In the Main Activity, I have BottomNavigationView where there are 3 different parent fragments. The parent fragment has recyclerview and on item click of recyclerview, I am launching child fragment for more details about the item. I am trying to implement Shared Element Transition between my two fragments (parent & child) but it's not happening.

There is no issue with launching child fragment also I have checked the transition name and it's the same in child fragment which I assign to an item in the adapter. I am using Random class to assign transition name to item as in single parent fragment I have many recyclerviews. Here is my code:

Adapter

    final String transition;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        transition = "transition" + new Random().nextInt(9999999);
        viewHolder.image.setTransitionName(transition);
    }
    viewHolder.container.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mCallback.itemClicked(i, book, viewHolder.image, transition);
        }
    });

Parent Fragment

@Override
public void itemClicked(int pos, Book book, View view, String transition) {
    MainActivity activity = (MainActivity) getActivity();
    ChildFragment myFragment = new ChildFragment();
    Bundle bundle = new Bundle();
    bundle.putString(IntentExtraKeys.TRANSITION_NAME, transition);
    myFragment.setArguments(bundle);
    activity.showFragmentWithTransition(this, myFragment, ChildFragment.class.getName(), view, transition);
}

Activity

public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
        current.setExitTransition(TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition));
        newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
        newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition));
    }

    FragmentManager manager = current.getChildFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    transaction.replace(R.id.child_fragment, newFragment, tag);
    transaction.addToBackStack(tag);
    transaction.addSharedElement(sharedView, sharedElementName);
    transaction.commit();
}

default_transition

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

Child Fragment

    Bundle b = getArguments();
    if (b != null) {
        String transitionName = b.getString(IntentExtraKeys.TRANSITION_NAME);
        Logger.info("opening bundle book fragment:" + transitionName);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            image.setTransitionName(transitionName);
        }
    }

Here is the sample project of issue: https://gitlab.com/iskfaisal/transition-issue


Solution

  • I am trying to implement Shared Element Transition between my two fragments (parent & child) but it's not happening.

    Nothing is happening or at least it looks like nothing is happening because you're using only <changeTransform/> and <changeBounds /> in your default_transition.xml. If bounds of both Fragments coincide, there's nothing to "transit".

    However, if you add additional animation elements to the file then the transition is actually visible (even if bounds coincide).

    Introduction

    I have created a sample project similar to yours - BottomNavigationView with a NavHostFragment containing 2 parent Fragments - DashboardFragment and HomeFragment.

    Initially, DashboardFragment loads DashboardListFragment that consists of a simple RecyclerView. If any RecyclerView item is clicked then DashboardFragment loads DashboardDetailFragment (bounds in DashboardDetailFragment and DashboardListFragment coincide).

    Transition Behaviour

    Then, I pretty much reused your showFragmentWithTransition(...) and tried to click each element in the list to check if any transition would be visible - it wasn't.

    Hence, I modified the file by adding a simple <slide/> element as below:

    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet>
        <slide/>
        <changeTransform />
        <changeBounds />
    </transitionSet>
    

    And the sliding transition was right there.

    I also tried other elements like <fade/> or <explode/> or others - all of them worked just fine too.

    Conclusion

    Even if a transition is not visible, it doesn't mean it's not happening. You should try other animation elements to see it work.

    UPDATE

    Since you provided a link to your github code, I peeked into it and brought it to the working condition. I'll leave it up to you to improve it further.

    So, basically, your HomeFragment layout file contained both RecyclerView and a placeholder for your child Fragment at the same time. Instead, I split your HomeFragment into 2 entities - one for your parent Fragment HomeFragment containing only the placeholder for any child Fragment and HomeFragmentList containing your previous parent logic.

    Then, when a picture is selected, HomeFragment replaces your HomeFragmentList with a ChildFragment. And...it works!

    This way, you don't need to use your Activity for creating a child Fragment and it's more independent.

    Below, I provide the relevant code that had to be modified.

    Code

    HomeFragment (new parent)

    public class HomeFragment extends Fragment {
    
        public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_home, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            HomeFragmentList homeFragmentList = new HomeFragmentList();
            FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.child_fragment, homeFragmentList, homeFragmentList.getClass().getName());
            fragmentTransaction.commit();
        }
    }
    

    HomeFragmentList (old parent)

    public class HomeFragmentList extends Fragment implements AdapterListener {
        RecyclerView recyclerView;
        private ArrayList<String> images = new ArrayList<>();
    
        public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    
            View root = inflater.inflate(R.layout.fragment_home_list, container, false);
    
            recyclerView = root.findViewById(R.id.recycler_view);
            getImages();
            LinearLayoutManager manager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
            recyclerView = root.findViewById(R.id.recycler_view);
            recyclerView.setLayoutManager(manager);
            AdapterClass adapter = new AdapterClass(this, images);
            recyclerView.setAdapter(adapter);
    
            return root;
        }
    
        void getImages() {
            images.add("https://rukminim1.flixcart.com/image/832/832/book/0/1/9/rich-dad-poor-dad-original-imadat2a4f5vwgzn.jpeg?q=70");
            images.add("https://www.seeken.in/wp-content/uploads/2017/06/The-4-Hour-Work-Week.jpeg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/06/Managing-Oneself.jpeg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/07/How-to-Win-Friends-and-Influence-People.jpeg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/07/THINK-LIKE-DA-VINCI-7-Easy-Steps-to-Boosting-your-Everyday-Genius.jpeg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/07/How-To-Stop-Worrying-And-Start-Living.jpg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/08/THE-INTELLIGENT-INVESTOR.jpeg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/08/Awaken-the-Giant-within-How-to-Take-Immediate-Control-of-Your-Mental-Emotional-Physical-and-Financial-Life.jpg");
            images.add("https://www.seeken.in/wp-content/uploads/2017/08/E-MYTH-REVISITED.jpeg");
            images.add("https://images-na.ssl-images-amazon.com/images/I/41axGE4CehL._SX353_BO1,204,203,200_.jpg");
            images.add("https://rukminim1.flixcart.com/image/832/832/book/0/1/9/rich-dad-poor-dad-original-imadat2a4f5vwgzn.jpeg?q=70");
    
        }
    
        @Override
        public void itemClicked(int pos, ModelClass object, View view, String transition) {
            MainActivity activity = (MainActivity) getActivity();
            ChildFragment myFragment = new ChildFragment();
            Bundle bundle = new Bundle();
            bundle.putString("IMAGE_URL", object.getItem(pos));
            bundle.putInt("POSITION", pos);
            bundle.putString("TRANSITION_NAME", transition);
            myFragment.setArguments(bundle);
            Log.i("HOME FRAGMENT-DEBUG", transition + "/" + view.getTransitionName());
            showFragmentWithTransition(getParentFragment(), myFragment, ChildFragment.class.getName(), view, transition);
        }
    
        public void showFragmentWithTransition(Fragment current, Fragment _new, String tag, View sharedView, String sharedElementName) {
    
            FragmentManager manager = current.getChildFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            if (sharedView != null && sharedElementName != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    current.setSharedElementReturnTransition(TransitionInflater.from(getContext()).inflateTransition(R.transition.default_transition));
                    current.setExitTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.no_transition));
                    _new.setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(R.transition.default_transition));
                    _new.setEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.no_transition));
                    transaction.addSharedElement(sharedView, sharedElementName);
                    Log.i("ACTIVITY-DEBUG", sharedElementName + "/" + sharedView.getTransitionName());
                }
            }
            transaction.replace(R.id.child_fragment, _new, tag);
            transaction.addToBackStack(tag);
            transaction.commit();
        }
    }
    

    fragment_home.xml

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

    fragment_home_list.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout 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:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FFF">
    
            <androidx.core.widget.NestedScrollView
                android:id="@+id/home_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="15dp"
                    android:layout_marginLeft="9dp"
                    android:layout_marginRight="9dp">
    
    
                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/recycler_view"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="5dp"
                        android:orientation="vertical">
    
                    </androidx.recyclerview.widget.RecyclerView>
    
                </RelativeLayout>
    
            </androidx.core.widget.NestedScrollView>
    
            <com.google.android.material.appbar.AppBarLayout
                android:id="@+id/appbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FFF">
    
                <androidx.appcompat.widget.Toolbar
                    android:id="@+id/z_toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:elevation="4dp">
    
                    <RelativeLayout
                        android:id="@+id/header_container"
                        android:layout_width="match_parent"
                        android:layout_height="56dp">
    
                        <ImageView
                            android:id="@+id/logo"
                            android:layout_width="30dp"
                            android:layout_height="30dp"
                            android:layout_marginRight="10dp"
                            android:layout_centerVertical="true"
                            android:layout_alignParentLeft="true"
                            android:src="@mipmap/ic_launcher_round"
                            android:contentDescription="@string/app_name" />
    
                        <TextView
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_centerVertical="true"
                            android:layout_toRightOf="@+id/logo"
                            android:textColor="#000"
                            android:textSize="16sp"
                            android:text="Home" />
    
                    </RelativeLayout>
    
                </androidx.appcompat.widget.Toolbar>
    
            </com.google.android.material.appbar.AppBarLayout>
    
        </androidx.coordinatorlayout.widget.CoordinatorLayout>
    </RelativeLayout>
    

    Remark

    Please note that the transition for <changeTransform/> and <changeBounds /> is visible because bounds of a selected View in HomeFragmentList are different from that in the child Fragment.