如何使用 ViewGroup 作为过渡动画的共享元素?
How to use a ViewGroup as a shared element for a transition animation?
我正在尝试设置两个片段之间的“共享元素” 过渡动画。然而,我想要的目的地不是单一的视图,而是一个 FrameLayout
有两个共享大小的重叠元素(一个箭头和一个旋转地图)并且必须同时移动和收缩。
我的目标布局如下所示:
<FrameLayout
android:id="@+id/container_arrow"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ar.com.lichtmaier.antenas.ArrowView
android:id="@+id/arrow"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
我想把这一切当成一件事情。
在过渡之前,我在 container_arrow
上使用缩放和平移属性制作此动画,效果很好。
但是,当我使用过渡时,尺寸动画只影响外部 FrameLayout
,而不影响它的 children。内部箭头移动,但不是从小开始变大,而是从大开始并保持大。如果我改为瞄准箭头,它会起作用。
查看 ChangeBounds
转换代码,它似乎使用 setFrame()
直接调整目标元素的边界。这不会传播到它的 children.
我需要平移+收缩动画来影响两个元素,但转换名称必须是唯一的。有什么办法可以实现我想要的吗?
编辑:
我已经尝试通过调用将 FrameLayout 设置为一个组:
ViewCompat.setTransitionName(arrowContainer, "animatedArrow")
ViewGroupCompat.setTransitionGroup(arrowContainer, true) // <-- this
同样的事情.. =/
这正是 ViewGroupCompat.setTransitionGroup()
API (for API 14+ devices when using AndroidX Transition) or android:transitionGroup="true"
XML 属性(对于 API 21+ 设备)的用途 - 通过将该标志设置为 true,整个 ViewGroup 用作单个关于共享元素转换的项目。
请注意,您还必须在设置为过渡组的同一元素上设置过渡名称(使用 ViewCompat.setTransitionName()
/ android:transitionName
取决于您是否要支持回 API 14 岁或只有 API 21+).
我最终创建了自己的 Transition
子类,它类似于 ChangeBounds
但使用平移和缩放视图属性来移动目标而不是调整边界。计算平移增量并将其动画化为 0,还计算初始比例并将其动画化为 1。
代码如下:
class MoveWithScaleAndTranslation : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun getTransitionProperties() = properties
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
val values = transitionValues.values
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
values[PROPNAME_POSX] = screenLocation[0]
values[PROPNAME_POSY] = screenLocation[1]
values[PROPNAME_WIDTH] = view.width
values[PROPNAME_HEIGHT] = view.height
}
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if(startValues == null || endValues == null)
return null
val leftDelta = ((startValues.values[PROPNAME_POSX] as Int) - (endValues.values[PROPNAME_POSX] as Int)).toFloat()
val topDelta = ((startValues.values[PROPNAME_POSY] as Int) - (endValues.values[PROPNAME_POSY] as Int)).toFloat()
val scaleWidth = (startValues.values[PROPNAME_WIDTH] as Int).toFloat() / (endValues.values[PROPNAME_WIDTH] as Int).toFloat()
val scaleHeight = (startValues.values[PROPNAME_HEIGHT] as Int).toFloat() / (endValues.values[PROPNAME_HEIGHT] as Int).toFloat()
val view = endValues.view
val anim = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat("scaleX", scaleWidth, 1f),
PropertyValuesHolder.ofFloat("scaleY", scaleHeight, 1f),
PropertyValuesHolder.ofFloat("translationX", leftDelta, 0f),
PropertyValuesHolder.ofFloat("translationY", topDelta, 0f)
)
anim.doOnStart {
view.pivotX = 0f
view.pivotY = 0f
}
return anim
}
companion object {
private const val PROPNAME_POSX = "movewithscaleandtranslation:posX"
private const val PROPNAME_POSY = "movewithscaleandtranslation:posY"
private const val PROPNAME_WIDTH = "movewithscaleandtranslation:width"
private const val PROPNAME_HEIGHT = "movewithscaleandtranslation:height"
val properties = arrayOf(PROPNAME_POSX, PROPNAME_POSY, PROPNAME_WIDTH, PROPNAME_HEIGHT)
}
}
我正在尝试设置两个片段之间的“共享元素” 过渡动画。然而,我想要的目的地不是单一的视图,而是一个 FrameLayout
有两个共享大小的重叠元素(一个箭头和一个旋转地图)并且必须同时移动和收缩。
我的目标布局如下所示:
<FrameLayout
android:id="@+id/container_arrow"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ar.com.lichtmaier.antenas.ArrowView
android:id="@+id/arrow"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
我想把这一切当成一件事情。
在过渡之前,我在 container_arrow
上使用缩放和平移属性制作此动画,效果很好。
但是,当我使用过渡时,尺寸动画只影响外部 FrameLayout
,而不影响它的 children。内部箭头移动,但不是从小开始变大,而是从大开始并保持大。如果我改为瞄准箭头,它会起作用。
查看 ChangeBounds
转换代码,它似乎使用 setFrame()
直接调整目标元素的边界。这不会传播到它的 children.
我需要平移+收缩动画来影响两个元素,但转换名称必须是唯一的。有什么办法可以实现我想要的吗?
编辑:
我已经尝试通过调用将 FrameLayout 设置为一个组:
ViewCompat.setTransitionName(arrowContainer, "animatedArrow")
ViewGroupCompat.setTransitionGroup(arrowContainer, true) // <-- this
同样的事情.. =/
这正是 ViewGroupCompat.setTransitionGroup()
API (for API 14+ devices when using AndroidX Transition) or android:transitionGroup="true"
XML 属性(对于 API 21+ 设备)的用途 - 通过将该标志设置为 true,整个 ViewGroup 用作单个关于共享元素转换的项目。
请注意,您还必须在设置为过渡组的同一元素上设置过渡名称(使用 ViewCompat.setTransitionName()
/ android:transitionName
取决于您是否要支持回 API 14 岁或只有 API 21+).
我最终创建了自己的 Transition
子类,它类似于 ChangeBounds
但使用平移和缩放视图属性来移动目标而不是调整边界。计算平移增量并将其动画化为 0,还计算初始比例并将其动画化为 1。
代码如下:
class MoveWithScaleAndTranslation : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun getTransitionProperties() = properties
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
val values = transitionValues.values
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
values[PROPNAME_POSX] = screenLocation[0]
values[PROPNAME_POSY] = screenLocation[1]
values[PROPNAME_WIDTH] = view.width
values[PROPNAME_HEIGHT] = view.height
}
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if(startValues == null || endValues == null)
return null
val leftDelta = ((startValues.values[PROPNAME_POSX] as Int) - (endValues.values[PROPNAME_POSX] as Int)).toFloat()
val topDelta = ((startValues.values[PROPNAME_POSY] as Int) - (endValues.values[PROPNAME_POSY] as Int)).toFloat()
val scaleWidth = (startValues.values[PROPNAME_WIDTH] as Int).toFloat() / (endValues.values[PROPNAME_WIDTH] as Int).toFloat()
val scaleHeight = (startValues.values[PROPNAME_HEIGHT] as Int).toFloat() / (endValues.values[PROPNAME_HEIGHT] as Int).toFloat()
val view = endValues.view
val anim = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat("scaleX", scaleWidth, 1f),
PropertyValuesHolder.ofFloat("scaleY", scaleHeight, 1f),
PropertyValuesHolder.ofFloat("translationX", leftDelta, 0f),
PropertyValuesHolder.ofFloat("translationY", topDelta, 0f)
)
anim.doOnStart {
view.pivotX = 0f
view.pivotY = 0f
}
return anim
}
companion object {
private const val PROPNAME_POSX = "movewithscaleandtranslation:posX"
private const val PROPNAME_POSY = "movewithscaleandtranslation:posY"
private const val PROPNAME_WIDTH = "movewithscaleandtranslation:width"
private const val PROPNAME_HEIGHT = "movewithscaleandtranslation:height"
val properties = arrayOf(PROPNAME_POSX, PROPNAME_POSY, PROPNAME_WIDTH, PROPNAME_HEIGHT)
}
}