自訂共用元素轉換

如要自訂共用元素轉換動畫的執行方式,可以使用幾個參數變更共用元素轉換方式。

動畫規格

如要變更用於大小和位置動作的動畫規格,可以在 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 和縮放)。

它會在其他非共用的 UI 元素上方顯示,轉換完成後,該元素就會從重疊移至其專屬的 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() 的裁剪路徑。

如要支援將特定 UI 元素 (例如底部列或懸浮動作按鈕) 保留在共用元素轉換的上方,請使用 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

(請留意清單中其他項目如何因應持續增長的項目)