SwipeToDismissBox コンポーネントを使用すると、ユーザーはアイテムを左右にスワイプして、アイテムを閉じたり更新したりできます。
API サーフェス
SwipeToDismissBox コンポーザブルを使用して、スワイプ操作によってトリガーされるアクションを実装します。主なパラメータは次のとおりです。
state: スワイプ アイテムの計算によって生成された値を保存するために作成されたSwipeToDismissBoxState状態。生成時にイベントをトリガーします。backgroundContent: アイテム コンテンツの背後に表示されるカスタマイズ可能なコンポーザブル。コンテンツがスワイプされると表示されます。
基本的な例: スワイプで更新または閉じる
この例のスニペットは、スワイプの実装を示しています。この実装では、開始から終了までスワイプするとアイテムが更新され、終了から開始までスワイプするとアイテムが閉じられます。
data class TodoItem( val itemDescription: String, var isItemDone: Boolean = false )
@Composable fun TodoListItem( todoItem: TodoItem, onToggleDone: (TodoItem) -> Unit, onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, ) { val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { if (it == StartToEnd) onToggleDone(todoItem) else if (it == EndToStart) onRemove(todoItem) // Reset item when toggling done status it != StartToEnd } ) SwipeToDismissBox( state = swipeToDismissBoxState, modifier = modifier.fillMaxSize(), backgroundContent = { when (swipeToDismissBoxState.dismissDirection) { StartToEnd -> { Icon( painter = rememberVectorPainter(if (todoItem.isItemDone) AppIcons.CheckBox else AppIcons.CheckBoxOutlineBlank), contentDescription = if (todoItem.isItemDone) "Done" else "Not done", modifier = Modifier .fillMaxSize() .background(Color.Blue) .wrapContentSize(Alignment.CenterStart) .padding(12.dp), tint = Color.White ) } EndToStart -> { Icon( painter = rememberVectorPainter(AppIcons.Delete), contentDescription = "Remove item", modifier = Modifier .fillMaxSize() .background(Color.Red) .wrapContentSize(Alignment.CenterEnd) .padding(12.dp), tint = Color.White ) } Settled -> {} } } ) { ListItem( headlineContent = { Text(todoItem.itemDescription) }, supportingContent = { Text("swipe me to update or remove.") } ) } }
コードに関する主なポイント
swipeToDismissBoxStateはコンポーネントの状態を管理します。アイテムとのインタラクションが完了すると、confirmValueChangeコールバックがトリガーされます。コールバックの本文は、さまざまなアクションを処理します。コールバックは、コンポーネントに閉じるアニメーションを表示するかどうかを示すブール値を返します。この場合、次のようになります。- アイテムが開始から終了までスワイプされると、
onToggleDoneラムダが呼び出され、現在のtodoItemが渡されます。これは、ToDo アイテムの更新に対応します。 - アイテムが末尾から先頭にスワイプされると、
onRemoveラムダが呼び出され、現在のtodoItemが渡されます。これは、ToDo アイテムの削除に対応します。 it != StartToEnd: この行は、スワイプの方向がStartToEndでない場合はtrueを返し、それ以外の場合はfalseを返します。falseを返すと、SwipeToDismissBoxが「切り替え完了」スワイプの直後に消えるのを防ぎ、視覚的な確認やアニメーションが可能になります。
- アイテムが開始から終了までスワイプされると、
SwipeToDismissBoxを使用すると、各アイテムで水平スワイプ操作が可能になります。静止状態では、コンポーネントの内部コンテンツが表示されますが、ユーザーがスワイプを開始すると、コンテンツが移動してbackgroundContentが表示されます。通常のコンテンツとbackgroundContentの両方が、親コンテナの完全な制約を取得して、自身をレンダリングします。contentはbackgroundContentの上に描画されます。この場合、次のようになります。backgroundContentは、SwipeToDismissBoxValueに基づく背景色を持つIconとして実装されます。Blue(StartToEndをスワイプしたとき)- ToDo アイテムを切り替えます。Red(EndToStartをスワイプしたとき)- ToDo アイテムを削除します。Settledの背景には何も表示されません。アイテムがスワイプされていない場合、背景には何も表示されません。- 同様に、表示される
Iconはスワイプの方向に応じて変化します。 StartToEndは、ToDo アイテムが完了している場合はCheckBoxアイコン、完了していない場合はCheckBoxOutlineBlankアイコンを表示します。EndToStartにDeleteアイコンが表示されます。
@Composable fun SwipeItemExample() { val todoItems = remember { mutableStateListOf( TodoItem("Pay bills"), TodoItem("Buy groceries"), TodoItem("Go to gym"), TodoItem("Get dinner") ) } LazyColumn { items( items = todoItems, key = { it.itemDescription } ) { todoItem -> TodoListItem( todoItem = todoItem, onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, onRemove = { todoItem -> todoItems -= todoItem }, modifier = Modifier.animateItem() ) } } }
コードに関する主なポイント
mutableStateListOf(...)は、TodoItemオブジェクトを保持できる監視可能なリストを作成します。このリストにアイテムが追加または削除されると、Compose はそれに依存する UI の部分を再コンポーズします。mutableStateListOf()内で、4 つのTodoItemオブジェクトが、それぞれ「Pay bills」、「Buy groceries」、「Go to gym」、「Get dinner」という説明で初期化されます。
LazyColumnは、todoItemsの縦方向にスクロールするリストを表示します。onToggleDone = { todoItem -> ... }は、ユーザーがオブジェクトを完了としてマークしたときにTodoListItem内から呼び出されるコールバック関数です。todoItemのisItemDoneプロパティを更新します。todoItemsはmutableStateListOfであるため、この変更により再コンポーズがトリガーされ、UI が更新されます。onRemove = { todoItem -> ... }は、ユーザーがアイテムを削除したときにトリガーされるコールバック関数です。todoItemsリストから特定のtodoItemを削除します。これにより再コンポーズも行われ、表示されているリストからアイテムが削除されます。animateItem修飾子が各TodoListItemに適用され、アイテムが閉じられたときに修飾子のplacementSpecが呼び出されます。これにより、アイテムの削除と、リスト内の他のアイテムの並べ替えがアニメーション化されます。
結果
次の動画は、上記のスニペットの基本的なスワイプして閉じる機能を示しています。
完全なサンプルコードについては、GitHub ソースファイルをご覧ください。
高度な例: スワイプ時に背景色をアニメーション化する
次のスニペットは、位置しきい値を組み込んで、スワイプ時にアイテムの背景色をアニメーション化する方法を示しています。
data class TodoItem( val itemDescription: String, var isItemDone: Boolean = false )
@Composable fun TodoListItemWithAnimation( todoItem: TodoItem, onToggleDone: (TodoItem) -> Unit, onRemove: (TodoItem) -> Unit, modifier: Modifier = Modifier, ) { val swipeToDismissBoxState = rememberSwipeToDismissBoxState( confirmValueChange = { if (it == StartToEnd) onToggleDone(todoItem) else if (it == EndToStart) onRemove(todoItem) // Reset item when toggling done status it != StartToEnd } ) SwipeToDismissBox( state = swipeToDismissBoxState, modifier = modifier.fillMaxSize(), backgroundContent = { when (swipeToDismissBoxState.dismissDirection) { StartToEnd -> { Icon( painter = rememberVectorPainter(if (todoItem.isItemDone) AppIcons.CheckBox else AppIcons.CheckBoxOutlineBlank), contentDescription = if (todoItem.isItemDone) "Done" else "Not done", modifier = Modifier .fillMaxSize() .drawBehind { drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress)) } .wrapContentSize(Alignment.CenterStart) .padding(12.dp), tint = Color.White ) } EndToStart -> { Icon( painter = rememberVectorPainter(AppIcons.Delete), contentDescription = "Remove item", modifier = Modifier .fillMaxSize() .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress)) .wrapContentSize(Alignment.CenterEnd) .padding(12.dp), tint = Color.White ) } Settled -> {} } } ) { OutlinedCard(shape = RectangleShape) { ListItem( headlineContent = { Text(todoItem.itemDescription) }, supportingContent = { Text("swipe me to update or remove.") } ) } } }
コードに関する主なポイント
drawBehindは、Iconコンポーザブルのコンテンツの背後にあるキャンバスに直接描画します。drawRect()は、キャンバスに長方形を描画し、描画スコープの境界全体を指定されたColorで塗りつぶします。
- スワイプすると、
lerpを使用してアイテムの背景色がスムーズに変化します。StartToEndからスワイプすると、背景色がライトグレーから青に徐々に変化します。EndToStartからスワイプすると、背景色が薄いグレーから赤に徐々に変化します。- ある色から次の色への移行量は、
swipeToDismissBoxState.progressによって決まります。
OutlinedCardは、リストアイテム間に視覚的な区切りをわずかに追加します。
@Composable private fun SwipeItemWithAnimationExample() { val todoItems = remember { mutableStateListOf( TodoItem("Pay bills"), TodoItem("Buy groceries"), TodoItem("Go to gym"), TodoItem("Get dinner") ) } LazyColumn { items( items = todoItems, key = { it.itemDescription } ) { todoItem -> TodoListItemWithAnimation( todoItem = todoItem, onToggleDone = { todoItem -> todoItem.isItemDone = !todoItem.isItemDone }, onRemove = { todoItem -> todoItems -= todoItem }, modifier = Modifier.animateItem() ) } } }
コードに関する主なポイント
- このコードの主なポイントについては、前のセクションの主なポイントをご覧ください。このセクションでは、同じコード スニペットについて説明しています。
結果
次の動画は、アニメーション化された背景色を使用した高度な機能を示しています。
完全なサンプルコードについては、GitHub ソースファイルをご覧ください。