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?
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.
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>
ListActivity.class
ActivityOptionsCompat option = ActivityOptionsCompat
.makeSceneTransitionAnimation(ListActivity.this, cardView, "card");
startActivity(intent, option.toBundle());
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();
}
});
}
Note: gist for layout and activities
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With