Компонент SwipeToDismissBox позволяет пользователю закрыть или обновить элемент, проведя по нему влево или вправо.
Поверхность API
Используйте composable-функцию 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( if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.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( imageVector = Icons.Default.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 private 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( if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.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( imageVector = Icons.Default.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.