Как улучшить UX приложения при работе с полями ввода
Сегодня хотелось бы поговорить о наболевшем, о пользовательском опыте на экранах с полями ввода. Как правило, именно с полей ввода начинается знакомство пользователя с приложением. Стоит отметить, что ответственность за UX лежит не только на дизайнере, но и на разработчике. Начнём с настроек клавиатуры.
Типы клавиатур
В Android доступно несколько типов клавиатуры для осуществления первичной фильтрации вводимых данных. Ниже будут приведены типы, доступные для Jetpack Compose.
Буквенные клавиатуры
К буквенным можно отнести клавиатуры с типом:
- Text (стандартная текстовая клавиатура);
- Ascii (клавиатура позволяет ограничить ввод только ASCII символов);
- Password (клавиатура позволяет вводить различные знаки препинания и цифры, при открытии клавиатура переключится на латинский алфавит);
- Email (клавиатура для ввода электронного адреса, имеет вспомогательные кнопки в нижнем ряду для ускорения ввода данных);
- Uri (клавиатура для ввода URI, имеет вспомогательные кнопки в нижнем ряду для ускорения ввода данных).
Числовые клавиатуры
К числовым можно отнести клавиатуры с типом:
- Number (клавиатура для ввода цифр и ряда символов);
- NumberPassword (клавиатура для ввода цифровых паролей, в отличие от Number не позволяет ввести ничего кроме цифр);
- Phone (клавиатура для ввода номера телефона и USSD-команд);
- Decimal (клавиатура для ввода десятичных дробей).
Указание типа клавиатуры в Jetpack Compose
Указать тип клавиатуры для поля ввода можно при помощи класса KeyboardOptions
.
TextField( keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Text, // Указываем тип клавиатуры ), ... )
Помимо типа клавиатуры класс KeyboardOptions
позволяет настраивать такие параметры как:
capitalization
- сообщает клавиатуре, следует ли автоматически использовать заглавные буквы в словах и предложениях;autoCorrect
- сообщает клавиатуре, следует ли выполнять автоисправление для введёного текста;imeAction
- сообщает клавиатуре о необходимости изменить внешний вид вспомогательной кнопки на клавиатуре. Об этом поговорим чуть позднее, в рамках данной статьи.
Пароли и типы клавиатур
Если вы когда-нибудь видели поведение клавиатуры при записи экрана на iOS, во время ввода пароля, то для Android стоит снизить ожидания.
При использовании XML для полей ввода указывался параметр android:inputType="textPassword"
, который включал маску для вводимых символов.
При использовании Jetpack Compose это работает по другому. Для создания маски-пароля требуется передать PasswordVisualTransformation()
в visualTransformation
. Установка keyboardType
в этом случае лишь подготовит клавиатуру к вводу пароля.
TextField( visualTransformation = PasswordVisualTransformation(), // Указываем маску для поля ввода пароля keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Password, // Указываем тип клавиатуры ), ... )
Однако, если требуется повысить уровень безопасности экрана ввода пароля, рекомендуется использовать SECURE_FLAG. Данный флаг предотвратит запись видео или создание скриншота на экране.
Типы действий на клавиатуре
На клавиатуре имеется кнопка действия (ImeAction).
Иногда использование данной кнопки является более удобным путём для пользователя, так как она находится в нижней части экрана. Например, не потребуется тянуться пальцем в верхнюю часть экрана.
Клавиатура поддерживает следующие действия:
Система самостоятельно выберет действие для клавиатуры. Как правило это None
или Done
.
При клике на данное действие курсор перейдёт на новую строку.
При клике на действие предполагается переход куда-либо. Например, переход по введенному URL.
При клике на действие предполагается выполнение поискового запроса по введённому значению.
При клике на действие предполагается отправка текста.
При клике на действие предполагается перевод фокуса на предыдущее поле ввода, если оно есть.
При клике на действие предполагается перевод фокуса на следующее поле ввода, если оно есть.
При клике на действие предполагается выполнение какого-либо завершающего действия (закрытие клавиатуры/отображение прогресса и отправка запроса).
Важно отметить, что иконки на ImeAction
могут отличаться в зависимости от выбранной системной клавиатуры.
Указание ImeAction в Jetpack Compose
Указание ImeAction
, как говорилось ранее, осуществляется с помощью класса KeyboardOptions
.
TextField( keyboardOptions = KeyboardOptions.Default.copy( imeAction = ImeAction.Search, // Указываем тип кнопки действия ), keyboardActions = KeyboardActions { // Указываем код, который требуется выполнить при клике на ImeAction }, ... )
Размещение контента над клавиатурой и другие проблемы, связанные с этим вопросом
На StackOverflow можно найти множество способов решения различных проблем, связанных с размещением контента над клавиатурой. Ниже я приведу примеры кейсов, с которыми мы сталкивались на наших проектах, и поделюсь решениями, к которым мы пришли.
Перекрытие контента прижатой к низу кнопкой при открытой клавиатуре
Периодически в дизайне можно встретить ситуацию, когда поле ввода находится в верхней части экрана, а кнопка прижата к низу экрана и при открытии клавиатуры требуется поднять кнопку, перекрывая контент кнопкой.
В данном случае мы рекомендуем установить значение adjustResize
для параметра windowSoftInputMode
для Activity. Например, через Manifest.
Если в проекте используется BottomSheetDialogFragment
, для него значение устанавливается отдельно через тему, так как данный класс имеет свой Window
. Дополнительно, стоит указывать метод imePadding
, который добавляет Spacer
равный размеру клавиатуры для корректного подъема контента.
Верстка с использованием Jetpack Compose при этом может выглядеть следующим образом:
Box(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { SomeHeader() SomeBody() } OverlayFooter() }
Деформация контента при использовании adjustResize
Помните, что когда вы работаете с adjustResize
, при недостатке места на экране вёрстка может начать деформироваться.
Не забывайте проверять экраны с полями ввода на эмуляторах или физических устройствах с разной диагональю и настройками масштабирования контента в системе.
При необходимости добавляйте для контейнера возможность вертикального скролла. Это позволит избежать деформации верстки при недостатке экранного пространства.
Поднятие контента над клавиатурой в BottomSheetDialogFragment
Достаточно часто в дизайне можно встретить боттомшит с полем ввода и кнопкой. Как правило, дизайнеры просят, чтобы при открытии клавиатуры весь контент включая кнопку поднимался над клавиатурой.
В этом случае, для BottomSheetDialogFragment следует указать adjustResize
через тему. Это сработает на устройствах большинства производителей.
Однако для некоторых устройств Huawei для кнопки стоит указать параметр BringIntoViewRequester, который позволит проскролить контент до элемента, тем самым поднимет кнопку над клавиатурой.
val bringIntoViewRequester = remember { BringIntoViewRequester() } val isFocused = remember { mutableStateOf(false) } TextField( modifier = Modifier .fillMaxWidth() .onFocusChanged{ isFocused.value = it.isFocused }, ... ) Button( onClick = { // Выполнить действие по клику }, modifier = Modifier .fillMaxWidth() .bringIntoViewRequester(bringIntoViewRequester), ) { Text(text = "Отправить") } ) LaunchedEffect(key1 = isFocused.value) { if (isFocused.value) { bringIntoViewRequester.bringIntoView() } }
Некорректная позиция handle в полях ввода внутри BottomSheetDialogFragment
Капелька, которая появляется при выделении или фокусе на тексте называется handle. Если вы встречались с поведением, как на скриншоте ниже, то у меня есть для вас крутая новость - это можно пофиксить.
Данный баг связан с некорректной передачей координат местоположения поля ввода на экране в метод отрисовки handle.
Данное поведение можно исправить разместив контент боттомшита внутри Box:
Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter, ) { // Контент боттомшита }
На этом наш пост подошёл к концу. Надеюсь, мой опыт работы с полями ввода поможет вам снизить количество граблей во время разработки функционала. Помните, что разработчик тоже влияет на UX приложения.