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()
を使用せず、代わりに新しい ripple API を使用します。そのため、LocalRippleTheme
はクエリされません。したがって、アプリで LocalRippleTheme
を設定した場合、マテリアル コンポーネントはこれらの値を使用しません。
次のセクションでは、移行せずに一時的に以前の動作にフォールバックする方法について説明しますが、新しい API に移行することをおすすめします。移行手順については、rememberRipple
から ripple
に移行すると後続のセクションをご覧ください。
移行せずにマテリアル ライブラリのバージョンをアップグレードする
ライブラリ バージョンのアップグレードのブロックを解除するには、一時的な LocalUseFallbackRippleImplementation CompositionLocal
API を使用して、以前の動作にフォールバックするようにマテリアル コンポーネントを構成します。
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
LocalIndication
を通じて古いリップルを提供できるように、必ず MaterialTheme
の外部で提供してください。
以降のセクションでは、新しい 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
からの移行
動作変更を一時的にオプトアウトする
マテリアル ライブラリには一時的な CompositionLocal
、LocalUseFallbackRippleImplementation
があります。これを使用すると、すべてのマテリアル コンポーネントを構成して、rememberRipple
を使用するようフォールバックできます。これにより、rememberRipple
は引き続き LocalRippleTheme
をクエリします。
次のコード スニペットは、LocalUseFallbackRippleImplementation CompositionLocal
API の使用方法を示しています。
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
マテリアル上に構築されたアプリのカスタムテーマを使用している場合は、アプリのテーマの一部としてコンポジション ローカルを安全に提供できます。
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
詳しくは、移行せずにマテリアル ライブラリのバージョンをアップグレードするをご覧ください。
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 { // ... } }
上記のスニペットを次のように変更する必要があります。
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { 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
であるかどうかを確認して、より軽量な実装を使用できるようにする必要があります。たとえば、作成されたノードが IndicationNodeFactory
の場合、Modifier.indication
は作成したノードに内部的にデリゲートします。そうでない場合は、Modifier.composed
を使用して rememberUpdatedInstance
を呼び出します。