Modifier.clickable を使用するインタラクティブ コンポーネントのコンポジション パフォーマンスを改善するために、新しい API が導入されました。これらの API を使用すると、リップルなど、より効率的な Indication 実装が可能になります。
androidx.compose.foundation:foundation:1.7.0+ と androidx.compose.material:material-ripple:1.7.0+ には、次の API の変更が含まれています。
非推奨 |
置き換え |
|---|---|
|
|
|
代わりに、マテリアル ライブラリで新しい 注: ここで「マテリアル ライブラリ」とは、 |
|
次のいずれかの手順を行います。
|
このページでは、動作変更の影響と、新しい API への移行手順について説明します。
動作の変更
次のライブラリ バージョンには、リップル動作の変更が含まれています。
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
これらのバージョンのマテリアル ライブラリでは、rememberRipple() は使用されなくなり、代わりに新しいリップル API が使用されます。その結果、LocalRippleTheme のクエリは行われません。そのため、アプリケーションで LocalRippleTheme を設定しても、マテリアル コンポーネントはこれらの値を使用しません。
以降のセクションでは、新しい API に移行する方法について説明します。
rememberRipple から ripple に移行する
マテリアル ライブラリを使用する
マテリアル ライブラリを使用している場合は、rememberRipple() を対応するライブラリの ripple() の呼び出しに直接置き換えます。この API は、マテリアル テーマ API から派生した値を使用してリップルを作成します。次に、返されたオブジェクトを Modifier.clickable や他のコンポーネントに渡します。
たとえば、次のスニペットでは非推奨の API が使用されています。
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
上記のスニペットを次のように変更する必要があります。
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
なお、ripple() はコンポーザブル関数ではなくなり、記憶する必要はありません。また、修飾子と同様に複数のコンポーネントで再利用できるため、割り当てを節約するために、リップル作成をトップレベルの値に抽出することを検討してください。
カスタム デザイン システムの実装
独自のデザイン システムを実装していて、以前に rememberRipple() とカスタム RippleTheme を使用してリップルを構成していた場合は、代わりに material-ripple で公開されているリップルノード API に委任する独自のリップル API を提供する必要があります。これにより、コンポーネントはテーマ値を直接使用する独自のリプルを使用できます。詳細については、RippleTheme から移行するをご覧ください。
RippleTheme から移行する
RippleTheme を使用して特定のコンポーネントのリップルを無効にする
material ライブラリと material3 ライブラリは RippleConfiguration と LocalRippleConfiguration を公開します。これらを使用すると、サブツリー内のリップルの外観を構成できます。RippleConfiguration と LocalRippleConfiguration は試験運用版であり、コンポーネントごとのカスタマイズのみを目的としています。これらの API では、グローバル/テーマ全体のカスタマイズはサポートされていません。このユースケースの詳細については、RippleTheme を使用してアプリ内のすべてのリップルをグローバルに変更するをご覧ください。
たとえば、次のスニペットでは非推奨の API が使用されています。
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
上記のスニペットを次のように変更する必要があります。
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
RippleTheme を使用して、特定のコンポーネントのリップルの色/アルファを変更する
前のセクションで説明したように、RippleConfiguration と LocalRippleConfiguration は試験運用版の API であり、コンポーネントごとのカスタマイズのみを目的としています。
たとえば、次のスニペットでは非推奨の API が使用されています。
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
上記のスニペットを次のように変更する必要があります。
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
RippleTheme を使用してアプリ内のすべてのリップルをグローバルに変更する
以前は、LocalRippleTheme を使用してテーマ全体のレベルでリップル動作を定義できました。これは、カスタム デザイン システムのコンポジション ローカルとリップル間の統合ポイントでした。一般的なテーマ設定プリミティブを公開する代わりに、material-ripple が createRippleModifierNode() 関数を公開するようになりました。この関数を使用すると、デザイン システム ライブラリで高階 wrapper 実装を作成できます。この実装では、テーマ値をクエリしてから、この関数で作成されたノードにリップル実装を委任します。
これにより、デザイン システムは必要なものを直接クエリし、material-ripple レイヤで提供されるものに準拠することなく、必要なユーザー構成可能なテーマ設定レイヤを最上位に公開できます。また、この変更により、テーマから暗黙的に派生するのではなく、リップル API 自体がコントラクトを定義するため、リップルが準拠するテーマ/仕様がより明示的になります。
ガイダンスについては、マテリアル ライブラリのリップル API の実装を参照し、必要に応じて、独自のデザイン システムに合わせてマテリアル コンポジション ローカルの呼び出しを置き換えてください。
Indication から IndicationNodeFactory に移行する
Indication を渡す
Modifier.clickable や Modifier.indication に渡すリップルを作成するなど、渡すだけの Indication を作成する場合は、変更を加える必要はありません。IndicationNodeFactory は Indication から継承されるため、すべてが引き続きコンパイルされ、動作します。
Indication を作成しています
独自の Indication 実装を作成している場合、ほとんどの場合、移行は簡単です。たとえば、プレスにスケール効果を適用する Indication を考えてみましょう。
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
この移行は次の 2 つの手順で行います。
ScaleIndicationInstanceをDrawModifierNodeに移行します。DrawModifierNodeの API サーフェスはIndicationInstanceと非常によく似ています。IndicationInstance#drawContent()と機能的に同等のContentDrawScope#draw()関数を公開します。この関数を変更し、Indicationではなく、ノード内でcollectLatestロジックを直接実装する必要があります。たとえば、次のスニペットでは非推奨の API が使用されています。
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
上記のスニペットを次のように変更する必要があります。
private class ScaleIndicationNode( private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
ScaleIndicationを移行してIndicationNodeFactoryを実装します。コレクション ロジックがノードに移動されたため、これはノード インスタンスの作成のみを担当する非常にシンプルなファクトリ オブジェクトです。たとえば、次のスニペットでは非推奨の API が使用されています。
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
上記のスニペットを次のように変更する必要があります。
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Indication を使用して IndicationInstance を作成する
ほとんどの場合、Modifier.indication を使用してコンポーネントの Indication を表示する必要があります。ただし、rememberUpdatedInstance を使用して IndicationInstance を手動で作成するまれなケースでは、Indication が IndicationNodeFactory かどうかを確認するように実装を更新する必要があります。これにより、より軽量な実装を使用できます。たとえば、Modifier.indication が IndicationNodeFactory の場合、内部的に作成されたノードに委任します。そうでない場合は、Modifier.composed を使用して rememberUpdatedInstance を呼び出します。