一、概念
共享元素过渡是一种在内容之间具有一致的可组合项之间的无缝过渡方式。它们通常用于导航,当用户在这些屏幕之间导航时,可以直观地将不同的屏幕连接起来。
SharedTransitionLayout | fun SharedTransitionLayout( 实现共享元素转换所需的最外层布局,提供了 SharedTransitionScope,可组合项需要位于该作用域中才能使用以下两个共享元素修饰符(子布局如果是通过调用而不是直接写在作用域内,需要将作用域传递给子布局)。 |
Modifier.sharedElement() | fun Modifier.sharedElement( 适用于内容相同。只有该元素在页面转换时可见。 |
Modifier.sharedBounds() | fun Modifier.sharedBounds( 适用于视觉上不同但有相同元素。不相同的元素在页面转换时可见变换。 |
SharedContentState | fun rememberSharedContentState(key: Any): SharedContentState 用于区分和匹配共享的元素 |
- 注意共享元素修饰符的顺序, 如果匹配的两个元素设置尺寸的修饰符顺序不同(如padding),会造成大小不一失去了“同一元素”的初衷。
二、基本使用
结合 AnimatedContent() 或 AnimatedVisibility() 使用,将内容变化的动画交由系统处理。
implementation "androidx.compose.animation:animation:1.7.0-beta01"
2.1 共享元素 sharedElement
共享了 PageOne 和 PageTwo 中的图片,从 PageOne 切换至 PageTwo 能够以这张图片作为过渡元素展示动画。
@Composable
fun Screen() {
var isShowDetail by remember { mutableStateOf(true) }
//使用共享元素布局
SharedTransitionLayout {
//将界面切换动画交给系统处理
AnimatedContent(targetState = isShowDetail) { targetState ->
if (targetState) {
PageOne(
onClick = { isShowDetail = false },
//共享作用域传递给子布局,为了使用sharedElement
sharedTransitionScope = this@SharedTransitionLayout,
//动画作用于传递给子布局,用于协调动画
animatedVisibilityScope = this@AnimatedContent
)
} else {
PageTwo(
onClick = { isShowDetail = true },
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@AnimatedContent
)
}
}
}
}
@Composable
fun PageOne(
onClick: () -> Unit,
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
Box(
modifier = Modifier.fillMaxSize().clickable { onClick() },
contentAlignment = Alignment.TopStart //图在左上角
) {
with(sharedTransitionScope) {
Image(
painter = painterResource(R.drawable.pic_baidu),
contentDescription = null,
modifier = Modifier.sharedElement( //sharedElement标记用于共享的元素
state = rememberSharedContentState(key = "image"), //key用于区分和匹配元素
animatedVisibilityScope = animatedVisibilityScope
)
)
}
}
}
@Composable
fun PageTwo(
onClick: () -> Unit,
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
Box(
modifier = Modifier.fillMaxSize().clickable { onClick() },
contentAlignment = Alignment.BottomEnd //图在右下角
) {
with(sharedTransitionScope) {
Image(
painter = painterResource(R.drawable.pic_baidu),
contentDescription = null,
modifier = Modifier.sharedElement( //sharedElement标记用于共享的元素
state = rememberSharedContentState(key = "image"), //key用于区分和匹配元素
animatedVisibilityScope = animatedVisibilityScope
)
)
}
}
}
2.2 共享边界 sharedBounds
三、自定义
3.1 “调整大小”模式 resizeMode
ScaleToBounds | RemeasureToBounds |
首先会使用先行(或目标)约束条件测量子布局。然后,系统会缩放子项的稳定布局,以适应共享边界。可以视为状态之间的“图形缩放”。 | 会根据目标尺寸,通过固定的动画约束条件重新测量并重新布局 sharedBounds 的子布局。边界大小变化(可能是每一帧的变化)都会触发重新测量。 |
- 对于 Text 可组合项,建议使用 ScaleToBounds,因为它可以避免对文本进行重新布局和重排到不同的行。对于宽高比不同的边界,以及如果您希望两个共享元素之间具有流畅的连续性,建议使用 RemeasureToBounds。
3.2 使用动画规范 boundsTransform
使用 AnimationSpec 来更改大小和位置移动,如文本随弧形运动而移动使用 keyframes。
val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
keyframes {
durationMillis = boundsAnimationDurationMillis
initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
targetBounds at boundsAnimationDurationMillis
}
}
Text(
"Cupcake", fontSize = 28.sp,
modifier = Modifier.sharedBounds(
rememberSharedContentState(key = "title"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = textBoundsTransform
)
)
3.3 跳至最终布局(避免文字重排版)Modifier.skipToLookahead()
默认情况下,在两个布局之间转换时,布局尺寸会在其起始状态和最终状态之间以动画形式显示。在为文本等内容添加动画效果时,这可能是不希望出现的行为。
使用 | 未使用 |