Приветствую! В этой статье мы создадим снэкбар с обратным отсчетом времени в Jetpack Compose. Эта функция идеально подходит для того, чтобы дать пользователям время обдумать важные действия, например удаление аккаунта.

Оставайтесь с нами, и давайте погрузимся в работу.

Создание снэкбара с обратным отсчетом 

Для начала определим composable-функцию SnackbarCountdown, которая визуально представляет таймер обратного отсчета.

@Composable
private fun SnackbarCountdown(
timerProgress: Float,
secondsRemaining: Int,
color: Color
) {
Box(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center
) {
Canvas(Modifier.matchParentSize()) {
// Определение обводки
val strokeStyle = Stroke(
width = 3.dp.toPx(),
cap = StrokeCap.Round
)
// Рисовка трека
drawCircle(
color = color.copy(alpha = 0.12f),
style = strokeStyle
)
// Рисовка прогресса
drawArc(
color = color,
startAngle = -90f,
sweepAngle = (-360f * timerProgress),
useCenter = false,
style = strokeStyle
)
}
// Отображение оставшихся секунд
Text(
text = secondsRemaining.toString(),
style = LocalTextStyle.current.copy(
fontSize = 14.sp,
color = color
)
)
}
}

Настройка функции CountdownSnackbar

Теперь, когда у нас есть composable-функция SnackbarCountdown, мы можем определить composable-функцию CountdownSnackbar, которая принимает несколько параметров для настройки внешнего вида и поведения.

@Composable
fun CountdownSnackbar(
snackbarData: SnackbarData,
modifier: Modifier = Modifier,
durationInSeconds: Int = 5,
actionOnNewLine: Boolean = false,
shape: Shape = SnackbarDefaults.shape,
containerColor: Color = SnackbarDefaults.color,
contentColor: Color = SnackbarDefaults.contentColor,
actionColor: Color = SnackbarDefaults.actionColor,
actionContentColor: Color = SnackbarDefaults.actionContentColor,
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
) {
// Здесь - реализация...
}
  • snackbarData ➜ данные для снэкбара;
  • modifier ➜ модификатор, который будет применен к снэкбару;
  • durationInSeconds ➜ продолжительность таймера обратного отсчета;
  • actionOnNewLine ➜ опция отображения действия в отдельной строке;
  • shape ➜ форма контейнера снэкбара;
  • containerColor, contentColor, actionColor, actionContentColor, dismissActionContentColor ➜ различные параметры цветовой стилизации.

Реализация CountdownSnackbar

Управление длительностью и состоянием снэкбара

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

val totalDuration = remember(durationInSeconds) { durationInSeconds * 1000 }
var millisRemaining by remember { mutableIntStateOf(totalDuration) }

LaunchedEffect(snackbarData) {
while (millisRemaining > 0) {
delay(40)
millisRemaining -= 40
}
snackbarData.dismiss()
}

Использование интервала в 40 миллисекунд приводит к обновлению прогресса со скоростью 25 кадров в секунду, что достаточно плавно для человеческого глаза. Вы можете настроить этот интервал под свои нужды.

Работа с кнопками action и dismiss

Для создания кнопок action и dismiss мы используем информацию, предоставленную snackbarData.

// Определите кнопку action, если указана метка actionLabel
val actionLabel = snackbarData.visuals.actionLabel
val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
@Composable {
TextButton(
colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
onClick = { snackbarData.performAction() },
content = { Text(actionLabel) }
)
}
} else {
null
}

// Определите кнопку dismiss, если снэкбар включает действие выключения
val dismissActionComposable: (@Composable () -> Unit)? = if (snackbarData.visuals.withDismissAction) {
@Composable {
IconButton(
onClick = { snackbarData.dismiss() },
content = {
Icon(Icons.Rounded.Close, null)
}
)
}
} else {
null
}

Отображение снэкбара 

Наконец, соберем все вместе и выведем на экран снэкбар.

Snackbar(
modifier = modifier.padding(12.dp), // Примените отступ вокруг снэкбара
action = actionComposable,
actionOnNewLine = actionOnNewLine,
dismissAction = dismissActionComposable,
dismissActionContentColor = dismissActionContentColor,
actionContentColor = actionContentColor,
containerColor = containerColor,
contentColor = contentColor,
shape = shape,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
SnackbarCountdown(
// Рассчитайте ход (прогресс) таймера
timerProgress = millisRemaining.toFloat() / totalDuration.toFloat(),
// Рассчитайте оставшиеся секунды
secondsRemaining = (millisRemaining / 1000) + 1,
color = contentColor
)
// Отобразите сообщение
Text(snackbarData.visuals.message)
}
}

Поздравляю! Мы успешно создали снэкбар. Полный код реализации вы найдете на GitHub Gist. Теперь посмотрим, как мы можем использовать снэкбар.

Практический пример

Рассмотрим практический пример. Представим, что пользователь хочет удалить аккаунт и у него есть 5 секунд, чтобы отменить это важное действие.

Box(Modifier.fillMaxSize()) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Определите SnackbarHostState для управления состоянием снэкбара
val snackbarHostState = remember { SnackbarHostState() }

Button(
modifier = Modifier.align(Alignment.Center),
onClick = {
scope.launch {
// Показать снэкбар
val result = snackbarHostState.showSnackbar(
message = "User account deleted.",
actionLabel = "UNDO",
duration = SnackbarDuration.Indefinite
)
// Обработка результата снэкбара
when (result) {
SnackbarResult.Dismissed -> {
Toast.makeText(context, "Deleted permanently", Toast.LENGTH_SHORT).show()
}
SnackbarResult.ActionPerformed -> {
Toast.makeText(context, "Deletion canceled", Toast.LENGTH_SHORT).show()
}
}
}
}
) {
Text("Delete Account")
}

// Создайте SnackbarHost для отображения снэкбара
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(BottomCenter)
) { data ->
// Используйте CountdownSnackbar
CountdownSnackbar(data)
}
}

Вывод:

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Kappdev: How to Create a Countdown Snackbar in Android with Jetpack Compose

Предыдущая статьяПриложение React Native с поддержкой Apple Watch и виджетов
Следующая статьяСтековая и кучная память в Kotlin