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?
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:
TransitionName
may be any string you want.
There is ViewCompat.setTransitionName
if you need support pre Lollipop devices.