Search code examples
androidandroid-fragmentsandroid-recyclerviewandroid-transitions

How to used a shared transition from RecyclerView to a Fragment


I've seen this questioned asked a number of times on here without an answer. I'm hoping if I ask it again maybe someone will be kind enough to help me with it.

I'm trying to implement a shared transition from an Item in a Recyclerview to a fragment.

I currently have my fragment transaction in the onBindView method of the adapter.

public void onClick(View v) {
    ...
    activity.getSupportFragmentMnager().beginTransaction()
            .addSharedElement(v, "SharedString")
            .replace(R.id.container, fragment2)
            .addToBackStack(null)
            .commit();
}

In the android docs, addSharedElement(view and string) confuse me. How do I give the view a unique ID and should I even be using v here?

Can the string be what ever I want?


Solution

  • here is my implementation. There is FragmentList with RecyclerView with images on each item. And there is FragmentDetail with only on big image. Image from FragmentList list item fly to FragmentDetail. TransitionName of animated view on FragmentList and FragmentDetail must be same. So I give unique transition name for each list item ImageView:

    imageView.setTransitionName("anyString" + position)

    Then I pass that string to FragmentDetail via setArguments and set big image transitionName to that string. Also we should provide Transition which describes how view from one view hierarchy animates to another viewhierarchy. After that we should pass that transition to fragment. I do it before FragmentTransaction:

     detailFragment.setSharedElementEnterTransition(getTransition());
     detailFragment.setSharedElementReturnTransition(getTransition());
    

    Or you can override getSharedElementEnterTransition and getSharedElementReturnTransition of Fragment and declare transition there. Here is full code:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            if(savedInstanceState == null) {
                getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.fragment_container, new FragmentList())
                        .commit();
            }
        }
    
        public void showDetail(View view) {
            String transitionName = view.getTransitionName();
    
            FragmentDetail fragment = new FragmentDetail();
            fragment.setArguments(FragmentDetail.getBundle(transitionName));
            fragment.setSharedElementEnterTransition(getTransition());
            fragment.setSharedElementReturnTransition(getTransition());
    
            getSupportFragmentManager()
                    .beginTransaction()
                    .addSharedElement(view, transitionName)
                    .replace(R.id.fragment_container, fragment)
                    .addToBackStack(null)
                    .commit();
        }
    
        private Transition getTransition() {
            TransitionSet set = new TransitionSet();
            set.setOrdering(TransitionSet.ORDERING_TOGETHER);
            set.addTransition(new ChangeBounds());
            set.addTransition(new ChangeImageTransform());
            set.addTransition(new ChangeTransform());
            return set;
        }
    }
    

    activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" />
    

    FragmentList:

    public class FragmentList extends Fragment {
    
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_list, container, false);
            RecyclerView rv = view.findViewById(R.id.recyclerview);
            rv.setAdapter(new ListAdapter());
            return view;
        }
    
        private class ListAdapter extends RecyclerView.Adapter {
            @Override
            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
                return new Holder(view);
            }
    
            @Override
            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
                Holder hold = (Holder) holder;
                hold.itemView.setOnClickListener(v -> {
                    ((MainActivity) getActivity()).showDetail(hold.imageView);
                });
                //unique string for each list item
                hold.imageView.setTransitionName("anyString" + position);
            }
    
            @Override
            public int getItemCount() {
                return 10;
            }
    
            private class Holder extends ViewHolder {
                ImageView imageView;
    
                public Holder(View view) {
                    super(view);
                    imageView = view.findViewById(R.id.image);
                }
            }
        }
    }
    

    fragment_list.xml

    <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
    

    FragmentDetail:

    public class FragmentDetail extends Fragment {
        private static final String NAME_KEY = "key";
    
        public static Bundle getBundle(String transitionName) {
            Bundle args = new Bundle();
            args.putString(NAME_KEY, transitionName);
            return args;
        }
    
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_detail, container, false);
            String transitionName = getArguments().getString(NAME_KEY);
            ImageView imageView = view.findViewById(R.id.image);
            imageView.setTransitionName(transitionName);
            return view;
        }
    }
    

    fragment_detail.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
    
        <ImageView
            android:id="@+id/image"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:src="@drawable/cat"/>
    </LinearLayout>
    

    item.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center">
    
        <ImageView
            android:id="@+id/image"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@drawable/cat"/>
    </LinearLayout>
    

    Here is result:

    enter image description here

    TransitionName may be any string you want. There is ViewCompat.setTransitionName if you need support pre Lollipop devices.