Pewpewkatchu
Pewpewkatchu

Reputation: 145

Android - Shared element transition between different scaleType of ImageView not animating properly (using Glide)

Note that this question is very similar to the question found here. There was no resolution, so I thought I'd provide some of my code and ask again.

Essentially, I am attempting to do a shared element transition of an ImageView between two fragments. My ultimate goal is to have an animation that looks very similar to that of the default image gallery. An important point is that I'm using Glide to load my images. The issue I'm running into is that I cannot get the scaleType to animate smoothly (from centerCrop to fitCenter).

I have one fragment containing a RecyclerView whose items each contain an ImageView. These ImageViews are meant to be thumbnails, and the XML looks like this:

<ImageView
    android:id="@+id/ivPhoto"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:layout_marginEnd="@dimen/large_spacing"
    android:scaleType="centerCrop"/>

The important/problematic part being scaleType="centerCrop"

I set the transition name and load the images in my RecyclerView adapter using the following:

holder.ivPhoto.setTransitionName(current.getPhotoFilepath());
Glide.with(holder.ivPhoto.getContext())
        .load(current.getPhotoFilepath())
        .dontTransform()
        .into(holder.ivPhoto);

When an item is clicked, the following code is run:

@Override
public void onItemClicked(AssetDetail assetDetail, ImageView imageView) {
    if (assetDetail.getPhotoFilepath() != null) {
        AssetDetailFragmentDirections.ActionAssetDetailFragmentToDisplayImageFragment action = AssetDetailFragmentDirections.actionAssetDetailFragmentToDisplayImageFragment(assetDetail.getPhotoFilepath());
        FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
                .addSharedElement(imageView, assetDetail.getPhotoFilepath())
                .build();
        NavHostFragment.findNavController(this).navigate(action, extras);
    }
}

This takes you to the second fragment, which essentially just contains the following ImageView (technically a PhotoView, I've tried with a normal ImageView too):

<com.github.chrisbanes.photoview.PhotoView
    android:id="@+id/ivImage"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter" />

I postpone the enter transition and set the transition I'd like to use:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    postponeEnterTransition();
    TransitionSet transitionSet = new TransitionSet()
            .addTransition(new ChangeBounds())
            .addTransition(new ChangeImageTransform())
            .setDuration(1500)
            .setOrdering(TransitionSet.ORDERING_TOGETHER)
            .setInterpolator(new FastOutSlowInInterpolator());
    setSharedElementEnterTransition(transitionSet);
}

And finally I get & set the transition name, load the image and start the postponed enter transition:

DisplayImageFragmentArgs args = DisplayImageFragmentArgs.fromBundle(getArguments());
binding.ivImage.setTransitionName(args.getImageFilepath());

Glide.with(requireContext())
        .load(args.getImageFilepath())
        .listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                startPostponedEnterTransition();
                return false;
            }

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

This results in this animation which is totally wrong.

If I swap the order of the transitions like this:

TransitionSet transitionSet = new TransitionSet()
        .addTransition(new ChangeImageTransform())
        .addTransition(new ChangeBounds())
        .setDuration(1500)
        .setOrdering(TransitionSet.ORDERING_TOGETHER)
        .setInterpolator(new FastOutSlowInInterpolator());

I get this animation which is closer, but the enter transition starts from a strange place, and the return transition is behind/inside the CardView that holds the ImageView.

Trying with some different transitions (as per this article)

TransitionSet transitionSet = new TransitionSet()
        .addTransition(new ChangeClipBounds())
        .addTransition(new ChangeTransform())
        .addTransition(new ChangeBounds())
        .setDuration(1500)
        .setOrdering(TransitionSet.ORDERING_TOGETHER)
        .setInterpolator(new FastOutSlowInInterpolator());

results in this. In this one, you can see the scaleType changes instantly and then the rest of the animation happens. I want the scaleType changes to be smoothly animated with everything else.

An interesting point with the final combination of transitions is that if I remove scaleType="centerCrop" from the ImageView in the RecyclerView it animates perfectly, but obviously this means that the images have their original aspect ratio and aren't proper thumbnails. See here.

The issue is clearly to do with the scaleType, but no matter what I try I just can't seem to get it to work. I've done a ton of Googling and reading, but found nothing that resolves the issue.

As far as I understand it, using the ChangeImageTransform is supposed to animate the scaleType, but using it with any combination of things I've tried (more than are listed here, including trying it together with ChangeTransform) doesn't seem to work, or at least not exactly how I want/expect it to. I'm beyond frustrated at this point.

If anyone could give me any assistance in resolving this I would greatly appreciate it.

Upvotes: 5

Views: 1433

Answers (1)

TalkLittle
TalkLittle

Reputation: 9101

The easiest solution is probably to switch to Picasso instead of Glide, as the asker of Shared Element with scaleType centerCrop transition is jumpy ended up doing.

The problem is the way Glide resizes images to fit into each ImageView for memory savings. If the thumbnail ImageView size differs from the ViewPager size, it breaks the ChangeImageTransform of the shared element transition.

First of all, your transition set should contain:

<changeClipBounds/>
<changeTransform/>
<changeBounds/>
<changeImageTransform/>

The key is adding to the Glide requests:

.dontTransform()
.override(screenWidth, screenHeight)

However, this will result in high memory usage in your Grid fragment, since each image will be loaded at a much larger size than needed for the thumbnails. You will experience slow scrolling and crashes.

Therefore you could load the image the old way by default, and then load the full screen sized images right before the transition, on click of the grid item. You would also need to defer the Pager fragment transaction until the full image is loaded, so that the transition can snapshot the new ImageView properties.

onClickGridItem() {
    Glide.with(imageView)
        .load(uri)
        .listener(RequestListener<Drawable> {
            onResourceReady() {
                openPagerFragment()
                return false
            }
        })
        .dontTransform()
        .override(screenWidth, screenHeight)
        .into(imageView)
}

This may result in an unwanted flicker when swapping between image sizes. You can avoid that by preloading the full size image before setting it to the ImageView:

Glide.with(imageView)
    .load(uri)
    .listener(RequestListener<Drawable> {
        onResourceReady() {
            Glide.with ... .override(screenWidth, screenHeight).into(imageView)
            return false
        }
    })
    .dontTransform()
    .preload(screenWidth, screenHeight)

Upvotes: 1

Related Questions