Android - ImageView 的不同scaleType 之间的共享元素转换无法正确动画(使用Glide)

Pew*_*chu 5 animation android shared-element-transition android-glide

请注意,这个问题与此处找到的问题非常相似。没有解决方案,所以我想我应该提供一些代码并再次询问。

本质上,我试图在两个片段之间进行 ImageView 的共享元素转换。我的最终目标是制作一个看起来与默认图片库非常相似的动画。重要的一点是我使用 Glide 来加载图像。我遇到的问题是我无法让scaleType 平滑地进行动画处理(从centerCrop 到fitCenter)。

我有一个包含 RecyclerView 的片段,其每个项目都包含一个 ImageView。这些 ImageView 是缩略图,XML 如下所示:

<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"/>
Run Code Online (Sandbox Code Playgroud)

重要/有问题的部分是scaleType="centerCrop"

我使用以下命令设置转换名称并在 RecyclerView 适配器中加载图像:

holder.ivPhoto.setTransitionName(current.getPhotoFilepath());
Glide.with(holder.ivPhoto.getContext())
        .load(current.getPhotoFilepath())
        .dontTransform()
        .into(holder.ivPhoto);
Run Code Online (Sandbox Code Playgroud)

单击某个项目时,将运行以下代码:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这将带您到第二个片段,它基本上只包含以下 ImageView(技术上是 PhotoView,我也尝试过使用普通 ImageView):

<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" />
Run Code Online (Sandbox Code Playgroud)

我推迟输入转换并设置我想要使用的转换:

@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);
}
Run Code Online (Sandbox Code Playgroud)

最后,我获取并设置转换名称,加载图像并启动推迟的输入转换:

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);
Run Code Online (Sandbox Code Playgroud)

这导致这个动画是完全错误的。

如果我像这样交换转换的顺序:

TransitionSet transitionSet = new TransitionSet()
        .addTransition(new ChangeImageTransform())
        .addTransition(new ChangeBounds())
        .setDuration(1500)
        .setOrdering(TransitionSet.ORDERING_TOGETHER)
        .setInterpolator(new FastOutSlowInInterpolator());
Run Code Online (Sandbox Code Playgroud)

我得到的动画更接近,但是输入过渡从一个奇怪的地方开始,并且返回过渡位于保存 ImageView 的 CardView 后面/内部。

尝试一些不同的转换(根据本文

TransitionSet transitionSet = new TransitionSet()
        .addTransition(new ChangeClipBounds())
        .addTransition(new ChangeTransform())
        .addTransition(new ChangeBounds())
        .setDuration(1500)
        .setOrdering(TransitionSet.ORDERING_TOGETHER)
        .setInterpolator(new FastOutSlowInInterpolator());
Run Code Online (Sandbox Code Playgroud)

结果是这样的。在此示例中,您可以看到scaleType 立即发生变化,然后动画的其余部分发生。我希望 scaleType 的更改能够与其他所有内容一起平滑地进行动画处理。

最终过渡组合的一个有趣的点是,如果我scaleType="centerCrop"从 RecyclerView 中的 ImageView 中删除它,它的动画效果会完美,但显然这意味着图像具有原始的宽高比,并且不是正确的缩略图。看这里

这个问题显然与scaleType有关,但无论我尝试什么,我似乎都无法让它工作。我已经进行了大量的谷歌搜索和阅读,但没有发现任何可以解决问题的方法。

据我了解,使用 ChangeImageTransform 应该为scaleType设置动画,但是将它与我​​尝试过的任何组合(比这里列出的更多,包括与 ChangeTransform 一起尝试)似乎不起作用,或者至少不完全是我想要/期望的那样。此时我已经非常沮丧了。

如果有人能给我任何帮助来解决这个问题,我将不胜感激。

Tal*_*tle 0

最简单的解决方案可能是切换到 Picasso 而不是 Glide,因为具有scaleType centerCrop 转换的共享元素的询问者最终会变得神经兮兮。

问题在于 Glide 调整图像大小以适合每个 ImageView 以节省内存的方式。如果缩略图 ImageView 大小与 ViewPager 大小不同,则会破坏共享元素转换的 ChangeImageTransform。

首先,您的转换集应包含:

<changeClipBounds/>
<changeTransform/>
<changeBounds/>
<changeImageTransform/>
Run Code Online (Sandbox Code Playgroud)

关键是添加到 Glide 请求中:

.dontTransform()
.override(screenWidth, screenHeight)
Run Code Online (Sandbox Code Playgroud)

但是,这将导致 Grid 片段占用大量内存,因为每个图像的加载大小都比缩略图所需的大小大得多。您会遇到滚动缓慢和崩溃的情况。

因此,您可以默认情况下以旧方式加载图像,然后在单击网格项时在转换之前加载全屏大小的图像。您还需要推迟 Pager 片段事务,直到加载完整图像,以便转换可以快照新的 ImageView 属性。

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

在图像尺寸之间交换时,这可能会导致不必要的闪烁。您可以通过在将全尺寸图像设置到 ImageView 之前预加载全尺寸图像来避免这种情况:

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