CardView上使用共享元素转场(Shared Element Transition)并带有圆角。

18

我已经花了几个星期的时间来解决这个问题,但仍然无法解决。

所以,我有一个包含ImageView的LinearLayout的CardView。

没有圆角

如果没有圆角,共享元素转换可以很好地工作。但是,当我将卡片添加半径(app:cardCornerRadius =“25dp”)时,共享元素转换看起来很丑,因为它会先删除半径,然后开始动画

具有半径的转换效果

第一种方法:ObjectAnimator

我创建了一个ObjectAnimator来动画化卡片上的半径值,并在动画结束后开始转换。

ObjectAnimator animator = ObjectAnimator
            .ofFloat(view, "radius", AppUtil.dpAsPixel(this, 25), 0);
animator.setDuration(150);
animator.addListener( // start new Activity with Transition );
animator.start();

这个方法是可行的,但效果不太好,因为过渡效果需要等待动画完成后才开始过渡。我需要的是在执行过渡到新活动时半径正在进行动画(类似于TransitionSet中的ORDERING_TOGETHER)。

第二种方法-改变图像变换

我阅读了一个StackOverflow文章,使用Transformation类,如ChangeImageTransform和ChangeBounds。

我按照建议定义了我的应用程序主题(my_transition包含ChangeImageTransform transitionSet)。

<item name="android:windowSharedElementEnterTransition">@transition/my_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/my_transition</item>

但是它不起作用...

第三种方法 - 朴素的

我的最后一次尝试是强制目标ImageView也具有25dp的半径。因为也许我的CardView被转换成正方形是因为目标ImageView是正方形,但是你可以猜到,它并不能起作用。

第四种方法 - 不使用CardView

如您所见,我正在使用企鹅图片并使用CardView来创建半径。我可以使用图像转换使图像变圆,但我仍然认为这不是创建共享元素过渡的正确方式...

我的问题是,是否有一种方法可以使带有CardView半径的共享元素过渡在不先删除半径的情况下工作?


你能展示一下转换文件吗?你指定了哪个视图进行转换? - azizbekian
@azizbekian,这是您需要的内容:https://gist.github.com/aldoKelvianto/ebc337d6766506171c2c285e97278805。XML和共享元素转换代码都很普通。 - aldok
这是你的 XML。但你在哪里指定要过渡的视图ID呢? - azizbekian
你如何启动下一个活动? - azizbekian
你尝试过将CardView添加到共享场景中吗?例如,可以将R.id.iv_image_cover替换为CardView - azizbekian
显示剩余2条评论
4个回答

21

我终于解决了。对于那些感兴趣的人,这是我的解决方法:

为什么在开始转换之前要删除半径?因为目标ImageView没有任何半径。

activity_detail.xml

<ImageView
    android:id="@+id/iv_image_cover"
    android:layout_width="match_parent"
    android:layout_height="250dp"
    android:scaleType="centerCrop"
    android:src="@{animal.imageRes}"
    android:transitionName="animalImage"
    tools:src="@drawable/acat"
/>

当我在不设置圆角的情况下使用CardView时,它并不明显,但实际上已经变成目标共享视图。

  1. 为了实现从有圆角到无圆角的转换,您需要将目标共享视图设置为圆形。我只需使用一个带有半径的CardView将其包装即可。

activity_detail.xml

<android.support.v7.widget.CardView
    android:id="@+id/card"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:transitionName="card"
    app:cardCornerRadius="25dp"
>

    <ImageView
        android:id="@+id/iv_image_cover"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:scaleType="centerCrop"
        android:src="@{animal.imageRes}"
        android:transitionName="animalImage"
        tools:src="@drawable/acat"
    />

</android.support.v7.widget.CardView>
  1. 请确保将您的makeSceneTransition更改为使用"card"而不是"animalImage"

ListActivity.class

ActivityOptionsCompat option = ActivityOptionsCompat
.makeSceneTransitionAnimation(ListActivity.this, cardView, "card");

startActivity(intent, option.toBundle());
  1. 在DetailActivity中,你可以在转换开始时启动一个半径动画。

DetailActivity.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().getSharedElementEnterTransition()
        .addListener(new Transition.TransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {
                ObjectAnimator animator = ObjectAnimator
                    .ofFloat(activityDetailBinding.card, "radius", 0);
                animator.setDuration(250);
                animator.start();
            }
        });
}
  1. 享受平稳过渡

最终动画

注意:布局的要点请参考 这里,活动列表请参考 这里


1
你的解决方案非常好用!我有一个问题,是因为这个转换导致活动启动变慢了吗?(打开活动需要2秒钟)你有什么想法吗? - Uttam Panchasara
@UttamPanchasara,您的意思是由于这个转换,活动启动需要更长的时间吗?根据我的经验,启动活动不应该花费很长时间,尤其不会超过2秒。为了测试这一点,您可以在设备设置中禁用动画/转换。如果仍然很慢,那么问题就不在动画/转换上。 - aldok
好的,但是在我的情况下,当我点击recyclerview的项目并移动到详细屏幕时,它会暂停打开详细屏幕,就像不流畅一样...有什么想法吗?我已经参考了几乎所有Google过渡示例。谢谢。 - Uttam Panchasara
2
退出转换怎么样? - ralphgabb
关于退出转换: 创建一个名为exiting的变量,默认设置为false。 覆盖onBackPressed并在其中将其设置为true,然后调用super.onBackPressed()或supportFinishAfterTransition() 在onTransitionStart中,检查exiting是否为true,如果是: ObjectAnimator animator = ObjectAnimator.ofFloat(card, "radius", resources.getDimension(R.dimen.news_item_card_radius)) animator.setDuration(250); animator.start(); - Hampel Előd

5

根据Ovidiu的回答,这里有一个可以动画化角落的工作转换

https://gist.github.com/StefanDeBruijn/d45807d386af0e066a03186fe00366e8

这可以通过编程方式添加到进入共享转换集中,也可以通过xml添加:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <transitionSet>
        <targets>
            <target android:targetId="@id/backdrop" />
        </targets>
        <!-- Custom transition to take care of rounded corner to square corners transition -->
        <transition
            class=".ChangeOutlineRadius"
            app:endRadius="@dimen/square_corner_radius"
            app:startRadius="@dimen/default_corner_radius" />
        <!-- Default shared element transitions -->
        <changeBounds />
        <changeTransform />
        <changeClipBounds />
        <changeImageTransform />
    </transitionSet>
</transitionSet>

不要忘记在attrs.xml中添加以下内容:
   <declare-styleable name="ChangeOutlineRadius">
        <attr name="startRadius" format="dimension" />
        <attr name="endRadius" format="dimension" />
    </declare-styleable>

这需要目标 ImageView 也设置了半径吗? - Niels Masdorp
@NielsMasdorp 不需要,只需将其作为match-parent子项添加到具有半径的CardView中即可。 - Stefan de Bruijn

1

我无法使用片段共享元素转换使其正常工作。在动画过程中,CardView的角半径将被忽略。以下是可以正常工作的方法:

fragment2.setEnterSharedElementCallback(new SharedElementCallback() {
    @Override
    public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {}

    @Override
    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
        ImageView sharedImageView = null;

        for (View view : sharedElements) {
            if (view instanceof ImageView) {
                sharedImageView = (ImageView) view;
                break;
            }
        }

        if (sharedImageView != null) {
            sharedImageView.setClipToOutline(true);

            ObjectAnimator.ofInt(sharedImageView, new Property<ImageView, Integer>(Integer.class, "outlineRadius") {
                @Override
                public Integer get(ImageView object) {
                    return 0;
                }

                @Override
                public void set(ImageView object, final Integer value) {
                    object.setOutlineProvider(new ViewOutlineProvider() {
                        @Override
                        public void getOutline(View view, Outline outline) {
                            outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), value);
                        }
                    });
                }
            }, 150, 0).setDuration(duration).start();
        }
    }

    @Override
    public void onRejectSharedElements(List<View> rejectedSharedElements) {}

    @Override
    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}
});

基本上,与其动画化CardView的圆角,ImageView本身具有其自己的圆角,而是对其进行动画化。
使用ViewOutlineProvider对ImageView的角进行圆角处理,可以使用ObjectAnimator在共享元素转换播放时动画化角半径。请注意,在ImageView上调用setClipToOutline(true)也是必需的,否则角将不会被裁剪。
回调的onSharedElementEnd方法将以所有共享元素的列表形式调用。请注意,我的示例代码仅处理正在共享的多个ImageView中的一个的角动画。如果您的转换共享多个ImageView,则还需要考虑它们。
还要注意,由于某种原因,在播放反向转换时也会调用相同的回调。
通过一些努力,这可以变成一个常规的转换,您只需将其添加到自动确定共享元素应该执行什么操作的共享元素转换集合中即可。

这个功能非常好用,我认为之前的任何负面评价都已经不重要了。这绝对是目前为止最优雅的解决方案。 - Andreas Rudolph

1
我尝试了这里的所有解决方案,但都没有对我起作用。我需要从centerInside缩放类型转换为fitCenter。当使用建议在cardview上应用动画的解决方案时,这会创建问题。(在这种情况下,缩放类型不会被动画化)。
因此,为了解决这个问题,在父ImageView本身中,我应用了一个带有圆角的属性:
    val radius = context.getDimensionPixelOffset(R.dimen.radius).toFloat()
    image_view.outlineProvider = object : ViewOutlineProvider() {
        override fun getOutline(view: View, outline: Outline) {
            outline.setRoundRect(
                0, 0, view.width,
                view.height, radius
            )
        }
    }
    image_view.clipToOutline = true

不需要做任何其他事情,过渡动画API会处理剩下的部分。
平安。 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接