Shared Element Transition + Fragment + RecyclerView + ViewPager
Asked Answered
M

1

5

I am implementing a gallery app, which has a Fragment that holds a RecyclerView with images, onClick of an image I go to ViewPager to cycle through images.
For now, I am trying to implement just the entry animation like in this video. The problem is the animation just doesn't work, I am obviously missing something (just showing code which is relevant to transitions):

ViewPager:

public class ViewPagerFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_viewpager, container, false);
        Transition transition = TransitionInflater.from(getContext()).inflateTransition(R.transition.fragment_transition);

        setSharedElementEnterTransition(transition);
        postponeEnterTransition();
        return view;
}

GridAdapter:

public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setPhotoImage(new File(mArrayOfPhotos.get(position).photo));
        ViewCompat.setTransitionName(holder.photoImage, mPhotoObjects.get(position).photo);
    }

    @Override
    public void onClick(View view) {
        // pass an image view that is being clicked... 
        // ...via listener to MainActivity from where ViewPagerFragment is started
        mListener.onImageSelected(photoImage, getAdapterPosition());
    }
}

In MainActivity I instantiate ViewPagerFragment in onClick:

@Override
public void onImageSelected(View view, int clickedImagePosition) {
    ViewPagerFragment fragment = new ViewPagerFragment();
    Bundle bundle = new Bundle();
    bundle.putInt(ViewPagerFragment.KEY_IMAGE_INDEX, clickedImagePosition);
    fragment.setArguments(bundle);
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    // view is an image view that was clicked
    fragmentTransaction.addSharedElement(view, ViewCompat.getTransitionName(view));
    fragmentTransaction.setReorderingAllowed(true);
    fragmentTransaction.replace(R.id.fragment_placeholder, fragment, VIEWPAGER_FRAGMENT);
    fragmentTransaction.addToBackStack(null);
    fragmentTransaction.commit();
}

And finally in ImageFragment(which is a single image in ViewPager) I have:

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mFullImage.setTransitionName(transitionName);

    Glide.with(getActivity())
            .load(myImage)
            .apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
            .listener(new RequestListener<Drawable>() {
                @Override
                public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                    getParentFragment().startPostponedEnterTransition();
                    return false;
                }

                @Override
                public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                    getParentFragment().startPostponedEnterTransition();
                    return false;
                }
            })
            .into(mFullImage);

    return view;
}

Transition name is not the problem, it is unique, and it is passed normally between all the fragments. I have followed this article and read many others, but it feels like a small bit is missing in my code.

EDIT:
I was able to localize the problem I think. The transition worked several times randomly, apparently, it happens because images are not ready when I am making the transition. I tried to call postponeEnterTransition() in MainActivity onCreate but still doesn't work. Images are passed to the ImageFragment through the Bundle. Still looking.

EDIT:

I believe I found the problem, I have a fragment, that has a TabLayout inside it, which has 2 fragments, one displaying all photos, and one displaying photos that are tagged as favorite. And they both use the same RecyclerView. The shared element transition works only if the photo is in one section at the same time, once I tag a photo as favorite it appears in all photos as well as favorite photos and the transition no longer works. It probably happens because the transition is no longer unique. Is there a way to solve this? Considering the fact that both fragments use the same RecyclerView, and the same adapter class.

Malinda answered 12/4, 2018 at 21:53 Comment(4)
Did you check this article too? mikescamell.com/shared-element-transitions-part-4-recyclerviewExplanatory
@Deepakkaku thanks a lot for the link, unfortunately, I checked it too, I followed mine because it's more detailed and recent. But I partly followed it too. I think there is a small error in my code.Malinda
What exactly do you mean by doesn't work ? Is it crashing app ? or just not animating properly? It would be better if you link any screencast or video showing what is wrong with animation. Thanks!Fizzy
@NileshDeokar thank you for your time! There is no animation at all, there is no difference if I add a shared element or not. I tried to change the duration in the XML file, but no change. I think the animation is not even applied for some reason.Malinda
M
3

I finally got it to work, the problem was in transition names. The transitions are also working with the support Transition library.
At first, the transition was not working at all, so I used a method that I found here to postpone the transition, it's a good link that explains transitions:

private void scheduleStartPostponedTransition(final View sharedElement) {
sharedElement.getViewTreeObserver().addOnPreDrawListener(
    new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
            getParentFragment().startPostponedEnterTransition();
            return true;
        }
    });
}

In my case, I have nested fragments, a ViewPagerFragment that contains a ViewPager and manages the fragments (ImageFragment) using ImageAdapter. I call postponeEnterTransition() from the ViewPagerFragment and that is why I am using getParentFragment().

I used the method scheduleStartPostoponedTransition in Glide onLoadFailed and onResourceReady().
The main problem was in my implementation. I have a fragment that contains a TabLayout with 2 tabs, each containing a fragment with a RecyclerView. One fragment shows all photos and other shows favorite photos. The transitions were working fine (after some alteration that I mentioned above) if a photo was only in one tab, but as soon as I marked it as favorite, it wouldn't work in either tab. Which means that two tabs had a photo with the same transition name.
My solution was to pass an integer to the GridAdapter depending on which fragment was shown (1 for all photos and 2 for favorite photos) and set it as a transition name:

public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setPhotoImage(new File(mArrayOfPhotos.get(position).photo));
        ViewCompat.setTransitionName(holder.photoImage, mPhotoObjects.get(position).photo + fragmentNumber);
    }
}

And then pass this number to the ImageFragment and set it there too. After that, it started to work because every photo now had a different transition name.
Also if you are using a support version of transitions make sure you have the support library version 27.0.0 or above, as they fixed the transitions in this version.

Malinda answered 18/4, 2018 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.