共有要素遷移アニメーションの実行方法をカスタマイズするには、共有要素の遷移方法を変更するために使用できるパラメータがいくつかあります。
アニメーションの仕様
サイズと位置の移動に使用されるアニメーション仕様を変更するには、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 仕様を使用しています。
boundsTransform パラメータを示す例サイズ変更モード
2 つの共有境界間でアニメーション化する場合は、resizeMode パラメータを RemeasureToBounds または ScaleToBounds に設定できます。このパラメータは、共有要素が 2 つの状態間でどのように遷移するかを決定します。ScaleToBounds は、まずルックアヘッド(またはターゲット)制約を使用して子レイアウトを測定します。次に、子の安定したレイアウトが、共有境界に収まるようにスケーリングされます。ScaleToBounds は、状態間の「グラフィカル スケール」と考えることができます。
一方、RemeasureToBounds は、ターゲット サイズに基づいてアニメーション化された固定制約を使用して、sharedBounds の子レイアウトを再測定して再レイアウトします。再測定は境界サイズの変更によってトリガーされます。これはフレームごとに発生する可能性があります。
Text コンポーザブルの場合は、ScaleToBounds をおすすめします。これにより、テキストの再レイアウトや別の行へのリフローを回避できます。RemeasureToBounds は、アスペクト比が異なる境界や、2 つの共有要素間で流動的な連続性を実現する場合におすすめです。
2 つのサイズ変更モードの違いは、次の例で確認できます。
|
|
|---|---|
共有要素を動的に有効または無効にする
デフォルトでは、sharedElement() と sharedBounds() は、ターゲット状態で一致するキーが見つかるたびにレイアウト変更をアニメーション化するように構成されています。ただし、ナビゲーションの方向や現在の UI 状態など、特定の条件に基づいてこのアニメーションを動的に無効にしたい場合があります。
共有要素遷移が発生するかどうかを制御するには、rememberSharedContentState() に渡される SharedContentConfig をカスタマイズします。isEnabled プロパティは、共有要素がアクティブかどうかを決定します。
次の例は、特定の画面間(A から B など)を移動するときにのみ共有遷移を有効にし、他の画面では無効にする構成を定義する方法を示しています。
SharedTransitionLayout { val transition = updateTransition(currentState) transition.AnimatedContent { targetState -> // Create the configuration that depends on state changing. fun animationConfig() : SharedTransitionScope.SharedContentConfig { return object : SharedTransitionScope.SharedContentConfig { override val SharedTransitionScope.SharedContentState.isEnabled: Boolean // For this example, we only enable the transition in one direction // from A -> B and not the other way around. get() = transition.currentState == "A" && transition.targetState == "B" } } when (targetState) { "A" -> Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } "B" -> { Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } } } } }
デフォルトでは、アニメーションの進行中に共有要素が無効になっても、進行中のアニメーションが誤って削除されないように、現在の進行中のアニメーションは完了します。アニメーションの進行中に要素を削除する必要がある場合は、SharedContentConfig インターフェースの shouldKeepEnabledForOngoingAnimation をオーバーライドして false を返すことができます。
最終レイアウトにスキップ
デフォルトでは、2 つのレイアウト間で遷移するときに、レイアウト サイズは開始状態と最終状態の間でアニメーション化されます。テキストなどのコンテンツをアニメーション化する場合、これは望ましくない動作となる可能性があります。
次の例は、説明テキスト「Lorem Ipsum」が 2 つの異なる方法で画面に表示されることを示しています。最初の例では、コンテナのサイズが大きくなるにつれて、テキストがリフローされます。2 つ目の例では、テキストが拡大してもリフローされません。Modifier.skipToLookaheadSize() を追加すると、拡大時にリフローが発生しなくなります。
|
|
|---|---|
クリップとオーバーレイ
共有要素を異なるコンポーザブル間で共有するには、遷移が開始されてデスティネーションの一致する要素に移動するときに、コンポーザブルのレンダリングがレイヤ オーバーレイに昇格されます。これにより、親の境界とそのレイヤ変換(アルファやスケールなど)をエスケープできます。
共有されていない他の 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 を共有要素の上に配置する必要があります。コンポーザブルに修飾子を追加すると、コンポーザブルは引き上げられたままになります。
|
|
|---|---|
共有されていないコンポーザブルをアニメーション化して、遷移前に他のコンポーザブルの上に表示したままにすることもできます。このような場合は、renderInSharedTransitionScopeOverlay().animateEnterExit() を使用して、共有要素遷移の実行時にコンポーザブルをアニメーション化します。
JetsnackBottomBar( modifier = Modifier .renderInSharedTransitionScopeOverlay( zIndexInOverlay = 1f, ) .animateEnterExit( enter = fadeIn() + slideInVertically { it }, exit = fadeOut() + slideOutVertically { it } ) )
まれなケースですが、共有要素をオーバーレイでレンダリングしないようにするには、sharedElement() の renderInOverlayDuringTransition を false に設定します。
共有要素のサイズの変更を兄弟レイアウトに通知する
デフォルトでは、sharedBounds() と sharedElement() は、レイアウトの移行時にサイズの変更を親コンテナに通知しません。
遷移時にサイズの変更を親コンテナに反映するには、placeholderSize パラメータを PlaceholderSize.AnimatedSize に変更します。これにより、アイテムが拡大または縮小されます。レイアウト内の他のすべてのアイテムは、この変更に対応します。
|
(1 つのアイテムが大きくなるにつれて、リスト内の他のアイテムが下に移動することに注目してください)。 |
|---|---|