Компонент SwipeToDismissBox позволяет пользователю закрыть или обновить элемент, проведя по нему влево или вправо.
API surface
Используйте компонент SwipeToDismissBox, чтобы реализовать действия, которые запускаются жестами пролистывания. Ключевые параметры:
state– состояниеSwipeToDismissBoxState, созданное для хранения значения, полученного в результате вычислений для элемента, который можно смахнуть. При получении значения запускаются события.backgroundContent– настраиваемая composable-функция, которая показывается за контентом элемента, когда пользователь проводит по нему пальцем.
Простой пример: обновление или закрытие по свайпу
Фрагменты в этом примере показывают реализацию пролистывания, которая либо обновляет элемент при пролистывании от начала до конца или закрывает элемент, когда пролистывается от конца к началу.
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. Это соответствует обновлению элемента списка дел. - Если элемент смахнуть справа налево, вызывается лямбда-выражение
onRemove, которому передается текущий элементtodoItem. Это соответствует удалению задачи. it != StartToEnd– эта строка возвращаетtrue, если направление пролистывания неStartToEnd, иfalseв противном случае. Возвратfalseпредотвращает немедленное исчезновениеSwipeToDismissBoxпосле свайпа "Переключить статус выполнения", позволяя показать визуальное подтверждение или анимацию.
- Если элемент смахнуть от начала до конца, вызывается лямбда-выражение
SwipeToDismissBoxпозволяет взаимодействовать с каждым элементом, проводя по нему пальцем по горизонтали. В состоянии покоя он показывает внутреннее содержимое компонента, но когда пользователь начинает проводить пальцем по экрану, содержимое перемещается и появляетсяbackgroundContent. Обычный контент иbackgroundContentполучают полные ограничения родительского контейнера для отрисовки.contentрисуется поверхbackgroundContent. В этом случае:backgroundContentреализован какIconс цветом фона на основеSwipeToDismissBoxValue:Blueпри пролистыванииStartToEnd– переключение задачи.Redпри проведении пальцем по экрануEndToStart– удаление задачи.- Для
Settledв фоне ничего не отображается. Когда элемент не смахивается, в фоне ничего не отображается. - Аналогично,
Icon, который отображается, адаптируется к направлению свайпа: StartToEndпоказывает значок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 перекомпонует части пользовательского интерфейса, которые от него зависят.- Внутри
mutableStateListOf()инициализируются четыре объектаTodoItemс соответствующими описаниями: "Оплатить счета", "Купить продукты", "Сходить в спортзал" и "Поужинать".
- Внутри
LazyColumnпоказывает вертикальный списокtodoItems.onToggleDone = { todoItem -> ... }– это функция обратного вызова, которая вызывается изTodoListItem, когда пользователь помечает объект как выполненный. Оно обновляет свойствоisItemDoneобъектаtodoItem. ПосколькуtodoItemsявляетсяmutableStateListOf, это изменение запускает рекомпозицию, обновляя пользовательский интерфейс.onRemove = { todoItem -> ... }– функция обратного вызова, которая активируется, когда пользователь удаляет товар. Удаляет указанный элементtodoItemиз спискаtodoItems. Это также вызывает рекомпозицию, и элемент будет удален из отображаемого списка.- Модификатор
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рисует непосредственно на холсте за контентом composable-функции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.