具有半径的 CardView 上的共享元素转换
Shared Element Transition on CardView with radius
我已经处理这个问题好几个星期了,但我仍然无法解决这个问题。
所以,我有一个包含 LinearLayout 和 ImageView 的 CardView。
如果没有这个半径,共享元素过渡将无缝运行。但是,当我向该 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();
这可行,但看起来不太好,因为过渡在开始过渡之前等待动画完成。我需要的是在过渡到新的 Activity 时半径是动画的(类似于 TransitionSet 中的 ORDERING_TOGETHER)。
第二种方法 - ChangeImageTransform
我读过 Whosebug post 使用转换 Class 像 ChangeImageTransform 和 ChangeBounds。
我确实按照建议定义了我的应用程序主题(my_transition 包含 ChangeImageTransform transitionSet)
<item name="android:windowSharedElementEnterTransition">@transition/my_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/my_transition</item>
但是不行..
第三种方法 - 朴素
我最后的尝试是强制目标 ImageView 的半径也为 25dp。因为可能因为目标ImageView是正方形,所以我的CardView变成了正方形,但是正如你所猜到的,它不起作用。
第四种方法 - 不使用 CardView
如您所见,我正在使用 Penguin 图像并使用 CardView 制作半径。我可以使用图像变换使图像变圆,但我仍然认为这不是创建共享元素过渡的正确方法..
我的问题是,有没有一种方法可以在不先移除半径的情况下使用 CardView 半径进行共享元素转换?
终于解决了。对于那些有兴趣的人,方法如下:
为什么它在开始过渡之前删除了半径?因为目标 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"
/>
当我使用没有radius的CardView时,它并不明显,但它实际上变成了目标共享视图。
- 要实现半径到 no-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>
- 请务必将 makeSceneTransition 更改为使用 "card" 而不是 "animalImage"
ListActivity.class
ActivityOptionsCompat option = ActivityOptionsCompat
.makeSceneTransitionAnimation(ListActivity.this, cardView, "card");
startActivity(intent, option.toBundle());
- 在 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();
}
});
}
- 享受平稳过渡
注意:layout and activities
的要点
我无法让它与 Fragment 共享元素转换一起使用。 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,您也需要考虑这些。
另请注意,由于某些原因,在播放反向转换时也会调用相同的回调。
通过一些努力,这可以变成一个常规的过渡,您只需将其添加到一组共享元素过渡中,自动确定如何处理共享元素。
根据 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>
我尝试了这里的所有解决方案,但没有任何效果。我需要从 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 会处理剩下的事情。
和平。 :)
我已经处理这个问题好几个星期了,但我仍然无法解决这个问题。
所以,我有一个包含 LinearLayout 和 ImageView 的 CardView。
如果没有这个半径,共享元素过渡将无缝运行。但是,当我向该 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();
这可行,但看起来不太好,因为过渡在开始过渡之前等待动画完成。我需要的是在过渡到新的 Activity 时半径是动画的(类似于 TransitionSet 中的 ORDERING_TOGETHER)。
第二种方法 - ChangeImageTransform
我读过 Whosebug post 使用转换 Class 像 ChangeImageTransform 和 ChangeBounds。
我确实按照建议定义了我的应用程序主题(my_transition 包含 ChangeImageTransform transitionSet)
<item name="android:windowSharedElementEnterTransition">@transition/my_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/my_transition</item>
但是不行..
第三种方法 - 朴素
我最后的尝试是强制目标 ImageView 的半径也为 25dp。因为可能因为目标ImageView是正方形,所以我的CardView变成了正方形,但是正如你所猜到的,它不起作用。
第四种方法 - 不使用 CardView
如您所见,我正在使用 Penguin 图像并使用 CardView 制作半径。我可以使用图像变换使图像变圆,但我仍然认为这不是创建共享元素过渡的正确方法..
我的问题是,有没有一种方法可以在不先移除半径的情况下使用 CardView 半径进行共享元素转换?
终于解决了。对于那些有兴趣的人,方法如下:
为什么它在开始过渡之前删除了半径?因为目标 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"
/>
当我使用没有radius的CardView时,它并不明显,但它实际上变成了目标共享视图。
- 要实现半径到 no-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>
- 请务必将 makeSceneTransition 更改为使用 "card" 而不是 "animalImage"
ListActivity.class
ActivityOptionsCompat option = ActivityOptionsCompat
.makeSceneTransitionAnimation(ListActivity.this, cardView, "card");
startActivity(intent, option.toBundle());
- 在 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();
}
});
}
- 享受平稳过渡
注意:layout and activities
的要点我无法让它与 Fragment 共享元素转换一起使用。 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,您也需要考虑这些。
另请注意,由于某些原因,在播放反向转换时也会调用相同的回调。
通过一些努力,这可以变成一个常规的过渡,您只需将其添加到一组共享元素过渡中,自动确定如何处理共享元素。
根据 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>
我尝试了这里的所有解决方案,但没有任何效果。我需要从 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 会处理剩下的事情。
和平。 :)