Пользовательский опыт (UX — User Experience) — это то, как пользователи воспринимают продукт и какие впечатления они получают от взаимодействия с ним.
В процессе разработки мобильного приложения важно ориентироваться на пользовательский опыт в ситуациях, когда от пользователя требуется подтвердить какие-либо действия (или, наоборот, отменить уже совершенное действие).
Если приложение отображает AlertDialog по поводу или без него, пользователю это вряд ли понравится.
Библиотека компонентов дизайна материалов имеет Закусочная — виджет, который используется для отображения сообщений внизу приложения.
Чем хороша закусочная:
- информирует пользователя о статусе процесса, который приложение выполнило или будет выполнять,
- не прерывает взаимодействие пользователя с приложением,
- может содержать дополнительную кнопку для программирования различных действий.
Использование снэкбара
Jetpack Compose уже имеет реализацию закусочной.В примере ниже это отображается после нажатия кнопки:
Код состоит из двух основных компонентов: ЗакусочнаяХозяин И SnackbarHostState .@Composable fun SnackbarSample() { val snackbarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() val modifier = Modifier Box(modifier.fillMaxSize()) { Button(onClick = { coroutineScope.launch { snackbarHostState.showSnackbar(message = "This is a Snackbar") } }) { Text(text = "Click me!") } SnackbarHost( hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter) ) } }
Их использование позволяет правильно отображать, скрывать и закрывать закусочную – в соответствии с рекомендациями по Material Design. Один раз можно отобразить одну закусочную — остальные будут ждать в очереди.
Добавить отмену в Snackbar
Давайте рассмотрим более сложный пример.Давайте реализуем снэкбар, который отменяет действие пользователя.
В нашем примере мы будем отображать список задач и отслеживать его изменения.
Это поможет нам Модель представления .
Упрощенный пример: @Composable
fun HomeList(taskViewModel: ListViewModel = viewModel()) {
Scaffold {
val list by remember(taskViewModel) {
taskViewModel.taskList
}.
collectAsState() LazyColumn { items( items = list, itemContent = { task -> ListItem( task = task, onCheckedChange = taskViewModel::onCheckedChange ) } ) } } } @Composable private fun ListItem(task: Task, onCheckedChange: (Task) -> Unit) { Row( modifier = Modifier .
fillMaxWidth() .
height(64.dp) .
padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = task.isCompleted, onCheckedChange = { onCheckedChange(task) })
Spacer(Modifier.width(8.dp))
Text(text = task.title)
}
}
Наша составная функция получает на вход Модель представления , который, в свою очередь, загружает список задач ( viewModel.taskList ) и вызывает функцию для проверки их статуса ( viewModel.onCheckedChange ).
Строительные леса используется для построения правильной структуры макета.
Далее по тексту станет понятно, как это будет связано с закусочной.
На данный момент мы имеем такой результат:
Наше приложение должно реализовать следующее бизнес-правило: В списке отображаются только незавершенные задачи.
После установки галочки задача должна быть немедленно удалена из списка.
Ответственный за реализацию этой логики Модель представления .
Теперь давайте реализуем функционал, который позволит пользователю отменить действие.
Прежде всего, давайте создадим лямбда-функцию, которая будет вызываться вместе со снекбаром, когда пользователь нажимает на флажок.
@Composable
fun HomeList(taskViewModel: ListViewModel = viewModel()) {
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
val onShowSnackbar: (Task) -> Unit = { task ->
coroutineScope.launch {
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "${task.title} completed",
actionLabel = "Undo"
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Timber.d("Snackbar dismissed")
SnackbarResult.ActionPerformed -> taskViewModel.onCheckedChange(task)
}
}
}
.
}
Давайте посмотрим на код:
- Строка №3: сохранить CoroutineScope (нужно позже при показе закусочной)
- Строка № 4: сохранить ScaffoldState , который содержит настроенный SnackbarHostState .
- Строка №6: вызов лямбда-функции, когда пользователь нажимает на флажок.
В качестве входного параметра он получает задачу, выбранную в списке.
- Строка №8: вызов функции приостановки показатьСнэкбар() и покажи закусочную.
С использованием ЗакусочнаяРезультат , мы понимаем, какие изменения произошли (как изменилось состояние).
- Строки № 12–14: обработка двух возможных результатов ( Уволен И Действие выполнено ).
Если кнопка не нажата, напишите сообщение в журнал.
Если кнопка нажата, Модель представления изменяет значение логической переменной выполнен к противоположному (от ложного к истинному).
Когда значение переменной isCompleted снова станет ЛОЖЬ , задача вернется в список.
data class Task(val id: Long, val title: String, var isCompleted: Boolean)
class ListViewModel : ViewModel() {
private val list = mutableListOf(
Task(1L, "Buy milk", false),
Task(2L, "Watch 'Call Me By Your Name'", false),
Task(3L, "Listen 'Local Natives'", false),
Task(4L, "Study about 'fakes instead of mocks'", false),
Task(5L, "Congratulate Rafael", false),
Task(6L, "Watch Kotlin YouTube Channel", false)
)
private val _taskList: MutableStateFlow<List<Task>> = MutableStateFlow(list)
val taskList: StateFlow<List<Task>>
get() = _taskList.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), listOf())
fun onCheckedChange(task: Task) {
list.find { it.id == task.id }?.
isCompleted = task.isCompleted.not()
_taskList.value = list.filter { it.isCompleted.not() }
}
}
Теперь мы можем связать нашу закусочную с Строительные леса и вызовите лямбда-функцию в качестве параметра метода onCheckedChange :
Scaffold(scaffoldState = scaffoldState) {
.
ListItem(
task = task,
onCheckedChange = { task ->
taskViewModel.onCheckedChange(task)
onShowSnackbar(task)
}
)
}
Так как у ScaffoldState имеет уже SnackbarHostState , который мы определили ранее, мы просто передаем его как параметр и получаем желаемый результат:
И вот еще
В процессе разработки я столкнулся с тем, что после нажатия на чекбокс появлялся и тут же пропадал снэкбар.Адам Пауэлл помог мне решить эту проблему в Котлинланг в Slack .
Проблема была в следующем: закусочная убиралась из очереди при звонке показатьЗакусочная был отменен.
Изначально я объявил запомнитьCoroutineScope в функции создания Пункт списка , которые формировали элементы списка задач.
Проблема была решена путем перемещения ее объявления выше.
Строительные леса .
Что дальше?
Полный исходный код этой статьи доступен здесь.суть репозитории (и в конце статьи).
Не пинайте слишком сильно за реализацию Модель представления .
Да, это примитивно и предназначено только для имитации «живых данных».
В реальном приложении данные будут поступать, например, из Поток связан с Комната .
Для своего приложения я добавил реализацию Undo в снэкбаре в этот запрос на вытягивание .
Если хотите, посмотрите.
Я надеюсь, что эта статья поможет вам создать собственную реализацию действий отмены в закусочных.
Спасибо за ваше внимание и Адаму Пауэллу за помощь с сопрограммами.
Полный исходный код @Composable
fun HomeList(taskViewModel: ListViewModel = viewModel()) {
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
val onShowSnackbar: (Task) -> Unit = { task ->
coroutineScope.launch {
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "${task.title} completed",
actionLabel = "Undo"
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Timber.d("Snackbar dismissed")
SnackbarResult.ActionPerformed -> taskViewModel.onCheckedChange(task)
}
}
}
Scaffold(
scaffoldState = scaffoldState,
topBar = { HomeTopBar() }
) {
val list by remember(taskViewModel) { taskViewModel.taskList }.
collectAsState() LazyColumn { items( items = list, key = { it.id }, itemContent = { task -> ListItem( task = task, onCheckedChange = { task -> taskViewModel.onCheckedChange(task) onShowSnackbar(task) } ) } ) } } } @Composable private fun ListItem(task: Task, onCheckedChange: (Task) -> Unit) { Row( modifier = Modifier .
fillMaxWidth() .
height(64.dp) .
padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox( checked = task.isCompleted, onCheckedChange = { onCheckedChange(task) } ) Spacer(Modifier.width(8.dp)) Text( text = task.title, style = MaterialTheme.typography.body1, overflow = TextOverflow.Ellipsis, maxLines = 1 ) } } @Composable private fun HomeTopBar() { TopAppBar { Box(modifier = Modifier.fillMaxSize()) { Text( modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.h5, text = "My tasks" ) } } } data class Task(val id: Long, val title: String, var isCompleted: Boolean) class ListViewModel : ViewModel() { private val list = mutableListOf( Task(1L, "Buy milk", false), Task(2L, "Watch 'Call Me By Your Name'", false), Task(3L, "Listen 'Local Natives'", false), Task(4L, "Study about 'fakes instead of mocks'", false), Task(5L, "Congratulate Rafael", false), Task(6L, "Watch Kotlin YouTube Channel", false) ) private val _taskList: MutableStateFlow<List<Task>> = MutableStateFlow(list) val taskList: StateFlow<List<Task>> get() = _taskList.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), listOf()) fun onCheckedChange(task: Task) { list.find { it.id == task.id }?.
isCompleted = task.isCompleted.not()
_taskList.value = list.filter { it.isCompleted.not() }
}
}
От переводчика: комментарии и правки приветствуются.Теги: #Android #Разработка Android #Разработка мобильных приложений #Дизайн мобильных приложений #Kotlin #Jetpack Compose #viewmodel #snackbar
-
Мобильные Устройства Против Игровых Консолей
19 Oct, 24 -
Подушка Спинки
19 Oct, 24 -
Есть Ли Шанс У Моок В России?
19 Oct, 24 -
Исправление Полноценного Исправления
19 Oct, 24 -
Palm Pre Выдает Себя За Apple Iphone
19 Oct, 24