Shared Element Transition on CardView with radius

你离开我真会死。 提交于 2019-11-28 23:12:23

问题


I've been working on this problem for weeks and I'm still unable to solve this problem.

So, I have a CardView that contains a LinearLayout with an ImageView.

Without that radius Shared Element Transition works seamlessly. However, when I add radius (app:cardCornerRadius="25dp") to that CardView, the Shared Element Transition looks ugly because it remove the radius first and then start the animation.

1st Approach: ObjectAnimator

I create ObjectAnimator to animate the radius value on card, and after the animation end it start the transition.

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

This works but it doesn't looks great, because the transition wait the animation to finish before starting the transition. What I need is the radius is animating while doing transition to new Activity (something like ORDERING_TOGETHER in TransitionSet).

2nd Approach - ChangeImageTransform

I've read a StackOverflow post to use Transformation Class like ChangeImageTransform and ChangeBounds.

I did define my application theme like it was suggested (my_transition contains ChangeImageTransform transitionSet)

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

But it doesn't work..

3rd Approach - Naive

My last attempt is to force the target ImageView to have a radius of 25dp too. Because maybe my CardView is transformed into square because the target ImageView is square, but as you can guess, it doesn't work.

4th Approach - Not Using CardView

As you can see, I'm using Penguin images and use CardView to make a radius. I can make the image rounded by using Image Transformation, but I still don't think that's the right way to create a Shared Element Transition..

And here is my question, is there a way to make a Shared Element Transition with CardView radius works without removing the radius first?


回答1:


I finally able to solve it. For those who are interested, here's how:

Why it remove radius before starting transition? Because the target ImageView doesn't have any radius.

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"
/>

When I use CardView without radius, it's not noticable, but it's actually turned into target Shared View.

  1. To achieve radius to no-radius transition you have to set the target Shared View to be rounded. I'm simply wrap it using a Card View (with radius).

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. Be sure to change your makeSceneTransition to use "card" instead of "animalImage"

ListActivity.class

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

startActivity(intent, option.toBundle());
  1. In the DetailActivity, you can start a radius animation when the transition start.

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. Enjoy the smooth transition

Note: gist for layout and activities




回答2:


Based on Ovidiu's answer, here is a working transition which can animate the corners

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

This can either be added programatically to the enter shared transition set or through 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>

Don't forget to add to your attrs.xml:

   <declare-styleable name="ChangeOutlineRadius">
        <attr name="startRadius" format="dimension" />
        <attr name="endRadius" format="dimension" />
    </declare-styleable>



回答3:


I couldn't get that to work with Fragment Shared Element Transitions. The CardView's corner radius would just be ignored during the animation. Here's something that does work:

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) {}
});

Basically, instead of animating the rounded corners of the CardView, the ImageView itself has its own rounded corners that are being animated instead.

The ImageView's corners are rounded with a ViewOutlineProvider, and an ObjectAnimator can be used to animate the corner radius while the shared element transition is playing. Note that calling setClipToOutline(true) on the ImageView is also required, otherwise the corners will not be clipped.

The onSharedElementEnd method of the callback will be called with a list of all shared elements. Note that my example code handles animating the corners of just one of the ImageViews that are being shared. If your transition shares multiple ImageViews, you will need to take those into account as well.

Also note that for some reason the same callback is also called when the reverse transition is played.

With some amount of effort, this could be turned into a regular Transition that you just add to the set of shared element transitions that automatically figure out what to do with the shared elements.



来源:https://stackoverflow.com/questions/42274607/shared-element-transition-on-cardview-with-radius

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!