Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 372
Faisal Shaikh Avatar asked Dec 20 '19 21:12

Faisal Shaikh


People also ask

How to transition between fragments?

At a high level, here's how to make a fragment transition with shared elements: Assign a unique transition name to each shared element view. Add shared element views and transition names to the FragmentTransaction . Set a shared element transition animation.

What are nested fragments?

Fragments represent a behavior or portion of UI in an Activity. Fragments are mostly used in tablets to divide screen and utilize screen space in efficient way. With Android 4.2 nested fragments are introduced, with which now you can embed fragments inside fragments.

What is child fragment manager?

The child FragmentManager is the one that handles Fragments contained within only the Fragment that it was added to. The other FragmentManager is contained within the entire Activity .


2 Answers

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.

like image 64
Anatolii Avatar answered Sep 29 '22 02:09

Anatolii


Shared element transition does not work properly with FragmentTransaction.add method because parent fragment is still alive and resumed. Try

transaction.replace(R.id.child_fragment, newFragment, tag);

Check this discussion thread for more information.

like image 41
Ankur Avatar answered Sep 29 '22 03:09

Ankur