自訂共用元素轉換

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

動畫規格

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

(請注意,清單中的其他項目會隨著一個項目的變化而向下移動)