Site icon AppTractor

Swipe-to-Dismiss в Compose Material 3

Компонент «Закрытие с помощью свайпа» позволяет пользователям закрывать или обновлять элемент, проводя пальцем влево или вправо. Compose Material 3 предлагает простой в использовании компонент, который делает все за нас.

Зачем еще одно руководство по компоненту

Недавно мне пришлось реализовать swipe-to-dismiss функцию в одном из моих тестовых проектов. Я обнаружил, что все онлайн-ресурсы, включая официальную документацию Android, ссылаются на устаревшую версию компонента.

В версии 1.4.0-alpha16 Compose Material 3 произошло изменение, связанное с SwipeToDismissBox:

Ссылки на confirmValueChange в SwipeToDismissBoxState были помечены как устаревшие. Вместо этого пользователям следует использовать коллбэк onDismissed API SwipeToDismissBox. (Iee780)

Исходя из этого, я решил опубликовать это руководство, чтобы помочь другим, кто хочет добавить функциональность закрытия с помощью свайпа в свои приложения.

Руководство по реализации

Мы собираемся добавить базовую реализацию swipe-to-dismiss в приложение, которое отображает список из 30 элементов. Это позволит с помощью свайпов влево и вправо либо пометить элемент как прочитанный, либо удалить его.

Шаг 0: предварительные условия

Убедитесь, что в ваш проект добавлена ​​зависимость Compose Material 3.

Если вы используете Compose BOM и хотите получить стабильную версию Compose Material 3 в составе пакета:

// libs.versions.toml
[versions]
composeBom = "2025.12.01"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }

// app/build.gradle.kts
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)

Если вы предпочитаете последнюю альфа-версию библиотеки, укажите версию явно:

// libs.versions.toml
[versions]
composeBom = "2025.12.01"
composeMaterial3 = "1.5.0-alpha11"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "composeMaterial3" }

// app/build.gradle.kts
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)

Шаг 1: создание состояния

Сначала создайте состояние закрытия, используя rememberSwipeToDismissBoxState. Это позволит нам контролировать поведение и определять, когда происходят свайпы.

Убедитесь, что используете версию без параметра confirmValueChange, так как она устарела!

val dismissState = rememberSwipeToDismissBoxState(  
 positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold)

Свойство positionalTreshold для меня до сих пор остается загадкой. Судя по названию и описанию, можно предположить, что оно контролирует, насколько далеко пользователь должен перетащить/провести пальцем по элементу, прежде чем будет запущено действие. Однако я пробовал использовать значение по умолчанию SwipeToDismissBoxDefaults.positionalTreshold, которое устанавливает его равным 56.dp, а также it, it * 0.1f и аналогичные значения, и не заметил никакой разницы. Если у вас есть какие-либо идеи, напишите в комментариях.

Шаг 2: настройка SwipeToDismissBox

Оберните содержимое вашего списка компонентом SwipeToDismissBox. Это компонент, который обеспечивает жесты смахивания для наших композабл элементов. Он требует четыре основных параметра:

val coroutineScope = rememberCoroutineScope()

SwipeToDismissBox(
    state = dismissState,
    onDismiss = { dismissValue ->
        when (dismissValue) {
            SwipeToDismissBoxValue.StartToEnd -> {
                coroutineScope.launch {
                    dismissState.reset()
                    onMarkAsRead()
                }
            }
            SwipeToDismissBoxValue.EndToStart -> {
                coroutineScope.launch {
                    dismissState.reset()
                    onDelete()
                }
            }
            SwipeToDismissBoxValue.Settled -> {
                // no action
            }
        }
    },
    backgroundContent = {
        // Background revealed during swipe
        BackgroundSwipeContent(dismissState)
    },
    content = {
        // Your list item content
        ListItemContent(item = item)
    }
)

Мы используем coroutineScope, чтобы иметь возможность вызывать dismissState.reset(), который вернет элемент в исходное положение после срабатывания действия. Затем вызывается onMarkAsRead после того, как движение свайпа завершится. Вы можете опустить этот вызов, если не хотите анимировать возвращение элемента в исходное положение.

Шаг 3: создание фонового контента

Фон — это то, что видят пользователи при свайпе. Мы можем изменять цвет и значок в зависимости от направления свайпа или прогресса, которые мы получаем из dismissState.

@Composable
private fun BackgroundSwipeContent(swipeDirection: SwipeToDismissBoxValue) {
    val color by animateColorAsState(
        when (swipeDirection) {
            SwipeToDismissBoxValue.StartToEnd -> Color(0xFF4CAF50)
            SwipeToDismissBoxValue.EndToStart -> Color(0xFFF44336)
            SwipeToDismissBoxValue.Settled -> Color.LightGray
        },
        label = "background color"
    )
    val alignment = when (swipeDirection) {
        SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart
        SwipeToDismissBoxValue.EndToStart -> Alignment.CenterEnd
        SwipeToDismissBoxValue.Settled -> Alignment.Center
    }
    val iconResId = when (swipeDirection) {
        SwipeToDismissBoxValue.StartToEnd -> R.drawable.ic_mark_read
        SwipeToDismissBoxValue.EndToStart -> R.drawable.ic_delete
        SwipeToDismissBoxValue.Settled -> R.drawable.ic_mark_read
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(color)
            .padding(horizontal = 20.dp),
        contentAlignment = alignment
    ) {
        Icon(
            painter = painterResource(iconResId),
            contentDescription = null,
            tint = Color.White
        )
    }
}

Шаг 4: использование в LazyColumn

При использовании с LazyColumn убедитесь, что указан стабильный ключ для корректного отслеживания элементов:

LazyColumn(modifier = Modifier.fillMaxSize()) {
    items(items = items, key = { it.id }) { item ->
        SwipeableListItem(
            item = item,
            onMarkAsRead = {
                // do something
            },
            onDelete = {
                // do something
            }
        )
    }
}

Шаг 5: конечный результат

После того, как мы обернули элемент в SwipeToDismissBox, мы получили следующее поведение, которое в точности соответствует нашим ожиданиям.

Полный пример можно найти на GitHub.

Заключение

Функция SwipeToDismissBox в Compose Material 3 предоставляет простой способ добавления жестов смахивания к элементам списка. Важные моменты, которые следует учитывать:

На этом всё. Поделитесь в комментариях своим опытом использования swipe-to-dismiss в Compose Material 3.

Источник

Exit mobile version