I'm using a RecyclerView to ViewPager shared element transition. The problem is that when the viewpager is paged to another image, after returning to the recyclerview, the imageview which first was animated is white.
Starting viewpager:
FragmentTransaction fragmentTransaction = getSupportFragmentManager()
.beginTransaction()
.setReorderingAllowed(true)
.addSharedElement(view, name)
.hide(recyclerFragment)
.add(R.id.main_frameLayout, viewpagerFragment, tag)
.commit();
Returning back to recyclerview fragment:
FragmentTransaction fragmentTransaction = getSupportFragmentManager()
.beginTransaction()
.setReorderingAllowed(true)
.addSharedElement(view, name)
.remove(viewpagerFragment)
.show(recyclerFragment)
.commit();
Edit: Please note that I know the positions and I'm setting the transition names correctly.
I have created sample example for you with "Recyclerview + ViewPager + SharedTransition"
First let me tell you about the problem in your code is that, you are adding and removing fragment instead of replacing it when starting viewpager and coming back from viewpager. Also you have to take integer variable that will hold current position of recyclerview as well viewpager.
Below is sample gif to show what i achieved:
First take "currentPosition" as int in your MainActivity as below:
public static int currentPosition;
Then create abstract "ImageData" class that will contains image array as below:
abstract class ImageData {
static final int[] IMAGE_DRAWABLES = {
R.drawable.android1,
R.drawable.android2,
R.drawable.android3,
R.drawable.android4,
R.drawable.android5,
};
}
Now create "GridAdapter" that will display list of images in recyclerView:
/**
* A fragment for displaying a grid of images.
*/
public class GridAdapter extends RecyclerView.Adapter<ImageViewHolder> {
/**
* A listener that is attached to all ViewHolders to handle image loading events and clicks.
*/
private interface ViewHolderListener {
void onLoadCompleted(ImageView view, int adapterPosition);
void onItemClicked(View view, int adapterPosition);
}
private final RequestManager requestManager;
private final ViewHolderListener viewHolderListener;
/**
* Constructs a new grid adapter for the given {@link Fragment}.
*/
public GridAdapter(Fragment fragment) {
this.requestManager = Glide.with(fragment);
this.viewHolderListener = new ViewHolderListenerImpl(fragment);
}
@Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.image_card, parent, false);
return new ImageViewHolder(view, requestManager, viewHolderListener);
}
@Override
public void onBindViewHolder(ImageViewHolder holder, int position) {
holder.onBind();
}
@Override
public int getItemCount() {
return IMAGE_DRAWABLES.length;
}
private static class ViewHolderListenerImpl implements ViewHolderListener {
private Fragment fragment;
private AtomicBoolean enterTransitionStarted;
ViewHolderListenerImpl(Fragment fragment) {
this.fragment = fragment;
this.enterTransitionStarted = new AtomicBoolean();
}
@Override
public void onLoadCompleted(ImageView view, int position) {
// Call startPostponedEnterTransition only when the 'selected' image loading is completed.
if (MainActivity.currentPosition != position) {
return;
}
if (enterTransitionStarted.getAndSet(true)) {
return;
}
fragment.startPostponedEnterTransition();
}
@Override
public void onItemClicked(View view, int position) {
// Update the position.
MainActivity.currentPosition = position;
// Exclude the clicked card from the exit transition (e.g. the card will disappear immediately
// instead of fading out with the rest to prevent an overlapping animation of fade and move).
((TransitionSet) fragment.getExitTransition()).excludeTarget(view, true);
ImageView transitioningView = view.findViewById(R.id.card_image);
fragment.getFragmentManager()
.beginTransaction()
.setReorderingAllowed(true) // Optimize for shared element transition
.addSharedElement(transitioningView, transitioningView.getTransitionName())
.replace(R.id.fragment_container, new ImagePagerFragment(), ImagePagerFragment.class
.getSimpleName())
.addToBackStack(null)
.commit();
}
}
/**
* ViewHolder for the grid's images.
*/
static class ImageViewHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
private final ImageView image;
private final RequestManager requestManager;
private final ViewHolderListener viewHolderListener;
ImageViewHolder(View itemView, RequestManager requestManager,
ViewHolderListener viewHolderListener) {
super(itemView);
this.image = itemView.findViewById(R.id.card_image);
this.requestManager = requestManager;
this.viewHolderListener = viewHolderListener;
itemView.findViewById(R.id.card_view).setOnClickListener(this);
}
void onBind() {
int adapterPosition = getAdapterPosition();
setImage(adapterPosition);
// Set the string value of the image resource as the unique transition name for the view.
image.setTransitionName(String.valueOf(IMAGE_DRAWABLES[adapterPosition]));
}
void setImage(final int adapterPosition) {
// Load the image with Glide to prevent OOM error when the image drawables are very large.
requestManager
.load(IMAGE_DRAWABLES[adapterPosition])
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource) {
viewHolderListener.onLoadCompleted(image, adapterPosition);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable>
target, DataSource dataSource, boolean isFirstResource) {
viewHolderListener.onLoadCompleted(image, adapterPosition);
return false;
}
})
.into(image);
}
@Override
public void onClick(View view) {
// Let the listener start the ImagePagerFragment.
viewHolderListener.onItemClicked(view, getAdapterPosition());
}
}
}
Now create adapter for viewpager also that will display images in viewpager after clicking item from recyclerView:
public class ImagePagerAdapter extends FragmentStatePagerAdapter {
public ImagePagerAdapter(Fragment fragment) {
// Note: Initialize with the child fragment manager.
super(fragment.getChildFragmentManager());
}
@Override
public int getCount() {
return IMAGE_DRAWABLES.length;
}
@Override
public Fragment getItem(int position) {
return ImageFragment.newInstance(IMAGE_DRAWABLES[position]);
}
}
First create xml and transitions for "ImagePagerFragment" as below:
fragment_image.xml
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/image_description"
android:scaleType="fitCenter"/>
create tansition folder under res directory:
image_shared_element_transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="375"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:transitionOrdering="together">
<changeClipBounds />
<changeTransform />
<changeBounds />
</transitionSet>
grid_exit_transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="375"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:startDelay="25">
<fade>
<targets android:targetId="@id/card_view" />
</fade>
</transitionSet>
Now create "ImagePagerFragment" for display images in viewPager
/**
* A fragment for displaying a pager of images.
*/
public class ImagePagerFragment extends Fragment {
private ViewPager viewPager;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
viewPager = (ViewPager) inflater.inflate(R.layout.fragment_pager, container, false);
viewPager.setAdapter(new ImagePagerAdapter(this));
// Set the current position and add a listener that will update the selection coordinator when
// paging the images.
viewPager.setCurrentItem(MainActivity.currentPosition);
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
MainActivity.currentPosition = position;
}
});
prepareSharedElementTransition();
// Avoid a postponeEnterTransition on orientation change, and postpone only of first creation.
if (savedInstanceState == null) {
postponeEnterTransition();
}
return viewPager;
}
/**
* Prepares the shared element transition from and back to the grid fragment.
*/
private void prepareSharedElementTransition() {
Transition transition =
TransitionInflater.from(getContext())
.inflateTransition(R.transition.image_shared_element_transition);
setSharedElementEnterTransition(transition);
// A similar mapping is set at the GridFragment with a setExitSharedElementCallback.
setEnterSharedElementCallback(
new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Locate the image view at the primary fragment (the ImageFragment that is currently
// visible). To locate the fragment, call instantiateItem with the selection position.
// At this stage, the method will simply return the fragment at the position and will
// not create a new one.
Fragment currentFragment = (Fragment) viewPager.getAdapter()
.instantiateItem(viewPager, MainActivity.currentPosition);
View view = currentFragment.getView();
if (view == null) {
return;
}
// Map the first shared element name to the child ImageView.
sharedElements.put(names.get(0), view.findViewById(R.id.image));
}
});
}
}
Now create "ImageFragment" that will open as viewpager item:
public class ImageFragment extends Fragment {
private static final String KEY_IMAGE_RES = "com.key.imageRes";
public static ImageFragment newInstance(@DrawableRes int drawableRes) {
ImageFragment fragment = new ImageFragment();
Bundle argument = new Bundle();
argument.putInt(KEY_IMAGE_RES, drawableRes);
fragment.setArguments(argument);
return fragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_image, container, false);
Bundle arguments = getArguments();
@DrawableRes int imageRes = arguments.getInt(KEY_IMAGE_RES);
// Just like we do when binding views at the grid, we set the transition name to be the string
// value of the image res.
view.findViewById(R.id.image).setTransitionName(String.valueOf(imageRes));
// Load the image with Glide to prevent OOM error when the image drawables are very large.
Glide.with(this)
.load(imageRes)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable>
target, boolean isFirstResource) {
// The postponeEnterTransition is called on the parent ImagePagerFragment, so the
// startPostponedEnterTransition() should also be called on it to get the transition
// going in case of a failure.
getParentFragment().startPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable>
target, DataSource dataSource, boolean isFirstResource) {
// The postponeEnterTransition is called on the parent ImagePagerFragment, so the
// startPostponedEnterTransition() should also be called on it to get the transition
// going when the image is ready.
getParentFragment().startPostponedEnterTransition();
return false;
}
})
.into((ImageView) view.findViewById(R.id.image));
return view;
}
}
All done here! You can run this code and get what i achieved in above gif. Here you can see i'm not adding or removing fragments. Just replacing current fragment with shared transition element.
Note: In this example i have only take 5 items here. if you want to take extra items and load dynamic urls then you can also do with this.