Shared Element Transition on CardView with radius
T

4

18

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 radius

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.

with radius transition

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?

Telesthesia answered 16/2, 2017 at 12:55 Comment(7)
Can you show the transition file? What view have you specified to be transitioned?Dongola
@Dongola here you go gist.github.com/aldoKelvianto/ebc337d6766506171c2c285e97278805 , nothing special with the xml and the shared element transition codeTelesthesia
This is your xml. But where do you specify what view iD you want to be transitioned?Dongola
How do you lunch next activity?Dongola
@Dongola here is the java code gist.github.com/aldoKelvianto/fe7d845331e3b1085d563cd9aac7ff9cTelesthesia
Have you tried add CardView to the shared scene? i.e. instead of adding R.id.iv_image_cover as a shared view add cardview.Dongola
@Dongola I just did what you suggest, still no luck.Telesthesia
T
21

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

final animation

Note: gist for layout and activities

Telesthesia answered 19/2, 2017 at 18:13 Comment(5)
Your solution works like charm!!, I have 1 question on this, Is because of this transition, can activity start more slower(activity taking 2 second in opening) any idea?Murielmurielle
do you mean because of this transition, activity takes longer time to start @UttamPanchasara? In my experience it shouldn't take significant time to start activity, especially not 2 seconds. To test this, you can disable animation/transition on the Device's Setting. If its still slow, then the problem is not in the animation/transition.Telesthesia
Okay,,, but In my case when I tap on item of recyclerview and on moving to detail screen, it take pause in opening detail screen like not opening smoothly... Any idea? I have refer almost all Google sample example of transitions. ThanksMurielmurielle
how about the exit transition ?Pinnacle
regarding the exit transition: create a variable named exiting, set it to false by default. override onBackPressed and inside it set it to true then call super.onBackPressed() or supportFinishAfterTransition() inside the onTransitionStart, check if the exiting is true and if so: ObjectAnimator animator = ObjectAnimator.ofFloat(card, "radius", resources.getDimension(R.dimen.news_item_card_radius)) animator.setDuration(250); animator.start();Iconium
T
5

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>
Tades answered 3/10, 2018 at 12:53 Comment(2)
Does this require the target ImageView to have a radius set as well?Brookweed
@NielsMasdorp no, just add it as a match-parent child into your CardView with radiusTades
O
1

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.

Olivier answered 1/10, 2018 at 13:10 Comment(1)
This works like a charm, and any previous downvotes are moot as far as I can see. This is by far the most elegant solution so far.Nucleate
A
1

I tried all the solutions here but nothing worked for me. I needed to go from centerInside scale type to fitCenter. This created problems when using solutions that suggested applying animation on cardview. (scale type is not animated in that case).

So to solve this problem, in parent ImageView itself, I applied a rounded corner with:

    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

There is not need to do anything else, Transition Animation API takes care of the rest.

Peace. :)

Ackler answered 12/5, 2021 at 6:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.