自定义共享元素过渡

如需自定义共享元素过渡动画的运行方式,可使用一些 可用于更改共享元素的转换方式的参数。

动画规范

若要更改用于尺寸和位置移动的动画规范,您可以 为 Modifier.sharedElement() 指定不同的 boundsTransform 参数。 这会提供初始 Rect 位置和目标 Rect 位置。

例如,要使上例中的文本随弧形移动, 动作,请指定 boundsTransform 参数以使用 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
    )
)

您可以使用任何 AnimationSpec。此示例使用了 keyframes 规范。

图 1. 显示不同 boundsTransform 参数的示例

调整大小模式

在两个共享边界之间添加动画效果时,您可以设置 resizeMode 形参 更改为 RemeasureToBoundsScaleToBounds。此参数决定了 共享元素在两种状态之间转换。先ScaleToBounds 使用先行(或目标)约束条件测量子布局。然后, 子级的稳定布局会进行缩放,以适应共享边界。 ScaleToBounds 可以看作是“图形比例”状态。

RemeasureToBounds 会根据目标大小,使用带动画的固定约束条件重新测量和重新布局 sharedBounds 的子布局。通过 边界大小变化会触发重新测量,这可能会使 呈现每一帧图像

对于 Text 可组合项,建议使用 ScaleToBounds,因为它可以避免将文本重新布局并重新流动到不同的行。适用于不同宽高比的边界 而如果您希望两个共通元素之间保持流畅的连续性, 建议使用 RemeasureToBounds

以下示例展示了这两种大小调整模式之间的区别:

ScaleToBounds

RemeasureToBounds

跳至最终布局

默认情况下,在两个布局之间切换时,布局尺寸会以动画形式呈现 初始状态和最终状态之间的差距。在为文本等内容添加动画时,这可能是一种不必要的行为。

以下示例展示了说明文字“Lorem Ipsum”进入 以两种不同的方式显示在屏幕上在第一个示例中, 会随着容器大小的变大而自动进入,而第二个示例则是 自动重排添加 Modifier.skipToLookaheadSize() 可防止重排 才能正常发挥作用

无 Modifier.skipToLookahead() - 请注意“Lorem Ipsum”文字重排

Modifier.skipToLookahead() - 请注意,“Lorem Ipsum”文本在动画开始时保持其最终状态

剪辑和叠加层

在 Compose 中创建共享元素时的一个重要概念是, 以便在不同的可组合项之间共享, 可组合项提升为层叠加层, 其匹配项在目标位置中的匹配项。这样做的效果是 父元素的边界及其图层转换(例如 alpha 和缩放)。

它将在其他非共享界面元素之上呈现,过渡完成后,该元素将从叠加层移至自己的 DrawScope

如需将共享元素裁剪为形状,请使用标准 Modifier.clip() 函数。请将其放置在 sharedElement() 之后:

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

如果您需要确保共享元素绝不会在父元素之外呈现 容器,可以在 sharedElement() 上设置 clipInOverlayDuringTransition。修改者 默认情况下,对于嵌套的共享边界,clipInOverlayDuringTransition 会使用裁剪 从父级 sharedBounds() 开始。

支持保留特定界面元素,例如底部栏或悬浮操作 按钮,在共享元素转换期间始终位于顶部,使用 Modifier.renderInSharedTransitionScopeOverlay()。默认情况下,此修饰符会在共享转场效果处于活跃状态期间将内容保留在叠加层中。

例如,在 Jetsnack 中,需要将 BottomAppBar 放置在 共享元素,直到屏幕不可见为止。添加修饰符 添加到可组合项上可使其保持提升状态。

不含 Modifier.renderInSharedTransitionScopeOverlay()

使用 Modifier.renderInSharedTransitionScopeOverlay()

有时,您可能希望非共享可组合项在呈现动画效果之外 在过渡之前停留在其他可组合项上。在这种情况下,请使用 renderInSharedTransitionScopeOverlay().animateEnterExit(),用于为 可组合项。

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

图 2. 在动画过渡时滑入和滑出底部应用栏

在极少数情况下,如果您希望共享元素不渲染在叠加层中,可以将 sharedElement() 上的 renderInOverlayDuringTransition 设置为 false。

在共享元素大小更改时通知同级布局

默认情况下,sharedBounds()sharedElement() 不会通知父级 任何尺寸的容器都会随着布局转换而发生变化。

为了在父级容器转换时将尺寸更改传播到父级容器, 将 placeHolderSize 参数更改为 PlaceHolderSize.animatedSize。这样做会导致项放大或缩小。布局中的所有其他项 更改。

PlaceholderSize.contentSize(默认)

PlaceholderSize.animatedSize

(请注意,列表中的其他项目如何随着一个项目的增长而下移)