Иногда необходимо переопределить поведение фокуса по умолчанию для элементов на экране. Например, вы можете захотеть сгруппировать составные объекты , запретить фокусировку на определенном составном объекте, явно запросить фокусировку на одном из них, захватить или отпустить фокус или перенаправить фокус на вход или выход. В этом разделе описывается, как изменить поведение фокуса, если значения по умолчанию не соответствуют вашим потребностям.
Обеспечьте последовательную навигацию с фокус-группами
Иногда Jetpack Compose не сразу угадывает правильный следующий элемент для навигации с вкладками, особенно когда в игру вступают сложные родительские Composables , такие как вкладки и списки.
Хотя поиск фокуса обычно следует порядку объявления Composables , в некоторых случаях это невозможно, например, когда один из Composables в иерархии представляет собой горизонтальную прокрутку, которая не полностью видна. Это показано в примере ниже.
Jetpack Compose может решить сосредоточить внимание на следующем элементе, ближайшем к началу экрана, как показано ниже, вместо того, чтобы продолжать идти по ожидаемому пути для однонаправленной навигации:

В этом примере видно, что разработчики не планировали, чтобы фокус перескакивал с вкладки «Шоколад» на первое изображение ниже, а затем обратно на вкладку «Выпечка» . Вместо этого они хотели, чтобы фокус продолжался на вкладках до последней вкладки, а затем фокусировался на внутреннем содержимом:

В ситуациях, когда важно, чтобы группа компонуемых объектов получала фокус последовательно, как в строке Tab из предыдущего примера, вам необходимо обернуть Composable в родительский элемент, который имеет модификатор focusGroup() :
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
Двунаправленная навигация ищет ближайший компонуемый элемент для данного направления — если элемент из другой группы находится ближе, чем не полностью видимый элемент в текущей группе, навигация выбирает ближайший. Чтобы избежать такого поведения, вы можете применить модификатор focusGroup() .
FocusGroup заставляет всю группу выглядеть как единое целое с точки зрения фокуса, но сама группа не получит фокус — вместо этого фокус получит ближайший ребенок. Таким образом, навигация знает, что перед выходом из группы необходимо перейти к не полностью видимому элементу.
В этом случае три экземпляра FilterChip будут сфокусированы перед элементами SweetsCard , даже если SweetsCards полностью видны пользователю и некоторая часть FilterChip может быть скрыта. Это происходит потому, что модификатор focusGroup сообщает менеджеру фокуса изменить порядок, в котором элементы фокусируются, чтобы навигация была проще и более согласованной с пользовательским интерфейсом.
Без модификатора focusGroup , если FilterChipC не был виден, навигация по фокусу обнаружила бы его последним. Однако добавление такого модификатора делает его не только доступным для обнаружения, но и, как и ожидают пользователи, он также получит фокус сразу после FilterChipB .
Создание составного фокусируемого объекта
Некоторые составные элементы являются фокусируемыми по своей конструкции, например кнопка или составной объект с прикрепленным к нему clickable модификатором. Если вы хотите специально добавить фокусируемое поведение к составному объекту, вы используете модификатор focusable :
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
Создание составного нефокусируемого объекта
Могут возникнуть ситуации, в которых некоторые из ваших элементов не должны участвовать в фокусе. В таких редких случаях вы можете использовать canFocus property чтобы исключить возможность фокусировки Composable .
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
Запросить фокус клавиатуры с помощью FocusRequester
В некоторых случаях вам может потребоваться явно запросить фокус в ответ на взаимодействие с пользователем. Например, вы можете спросить пользователя, хочет ли он возобновить заполнение формы, и если он нажмет «да», вы захотите перефокусировать первое поле этой формы.
Первое, что нужно сделать, — это связать объект FocusRequester с составным объектом, на который вы хотите переместить фокус клавиатуры. В следующем фрагменте кода объект FocusRequester связан с TextField путем установки модификатора Modifier.focusRequester :
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
Вы можете вызвать метод requestFocus FocusRequester для отправки фактических запросов на фокусировку. Вы должны вызывать этот метод вне контекста Composable (в противном случае он выполняется повторно при каждой рекомпозиции). В следующем фрагменте показано, как запросить систему переместить фокус клавиатуры при нажатии кнопки:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
Захват и освобождение фокуса
Вы можете использовать фокус, чтобы помочь пользователям предоставить нужные данные, необходимые вашему приложению для выполнения своей задачи — например, получение действующего адреса электронной почты или номера телефона. Хотя состояния ошибок информируют ваших пользователей о том, что происходит, вам может понадобиться поле с ошибочной информацией, чтобы оставаться в фокусе, пока она не будет исправлена.
Чтобы захватить фокус, вы можете вызвать метод captureFocus() , а затем освободить его с помощью метода freeFocus() , как в следующем примере:
val textField = remember { FocusRequester() } TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
Приоритет модификаторов фокуса
Modifiers можно рассматривать как элементы, имеющие только один дочерний элемент, поэтому, когда вы ставите их в очередь, каждый Modifier слева (или сверху) оборачивает Modifier , следующий за ним справа (или ниже). Это означает, что второй Modifier содержится внутри первого, так что при объявлении двух focusProperties работает только самый верхний, так как следующие содержатся в самом верхнем.
Чтобы уточнить концепцию, посмотрите следующий код:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
В этом случае focusProperties , указывающий item2 как правый фокус, не будет использоваться, поскольку он содержится в предыдущем; таким образом, будет использоваться item1 .
Используя этот подход, родитель также может сбросить поведение по умолчанию, используя FocusRequester.Default :
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Родитель не обязательно должен быть частью той же цепочки модификаторов. Родительский составной объект может перезаписать свойство фокуса дочернего составного объекта. Например, рассмотрим FancyButton , который делает кнопку не фокусируемой:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
Пользователь может снова сделать эту кнопку фокусируемой, установив для canFocus значение true :
FancyButton(Modifier.focusProperties { canFocus = true })
Как и любой Modifier , связанные с фокусом, они ведут себя по-разному в зависимости от порядка, в котором вы их объявляете. Например, код, подобный следующему, делает Box доступным для фокусировки, но FocusRequester не связан с этим объектом фокусировки, поскольку он объявлен после объекта фокусировки.
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
Важно помнить, что focusRequester связан с первым фокусируемым объектом в иерархии, поэтому этот focusRequester указывает на первый фокусируемый дочерний элемент. Если ничего недоступно, это ни на что не укажет. Однако, поскольку Box является фокусируемым (благодаря модификатору focusable() ), вы можете перейти к нему с помощью двунаправленной навигации.
В качестве другого примера подойдет любой из следующих вариантов, поскольку модификатор onFocusChanged() относится к первому фокусируемому элементу, который появляется после модификаторов focusable() или focusTarget() .
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) | Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
Перенаправить фокус при входе или выходе
Иногда вам необходимо предоставить очень специфический вид навигации, как показано на анимации ниже:

Прежде чем мы углубимся в то, как это создать, важно понять поведение поиска фокуса по умолчанию. Без каких-либо изменений, как только поиск фокуса достигнет элемента Clickable 3 , нажатие DOWN на D-Pad (или эквивалентной клавише со стрелкой) переместит фокус на все, что отображается под Column , выходя из группы и игнорируя тот, что справа. . Если доступных для фокусировки элементов нет, фокус никуда не перемещается, а остается на Clickable 3 .
Чтобы изменить это поведение и обеспечить нужную навигацию, вы можете использовать модификатор focusProperties , который помогает вам управлять тем, что происходит, когда поиск фокуса входит или выходит из Composable :
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
Можно направить фокус на конкретный Composable всякий раз, когда он входит или выходит из определенной части иерархии — например, когда в вашем пользовательском интерфейсе есть два столбца, и вы хотите быть уверены, что всякий раз, когда обрабатывается первый из них, фокус переключается на второй:

На этой гифке, как только фокус достигает « Clickable 3 Composable в Column 1», следующим объектом, на котором находится фокус, является Clickable 4 в другом Column . Этого поведения можно добиться, объединив focusDirection со значениями enter и exit внутри модификатора focusProperties . Им обоим нужна лямбда-выражение, которое принимает в качестве параметра направление, откуда исходит фокус, и возвращает FocusRequester . Эта лямбда-выражение может вести себя тремя разными способами: возврат FocusRequester.Cancel останавливает продолжение фокуса, а FocusRequester.Default не меняет его поведение. Если вместо этого предоставить FocusRequester прикрепленный к другому Composable фокус перейдет на этот конкретный Composable .
Изменить направление продвижения фокуса
Чтобы переместить фокус на следующий элемент или в точном направлении, вы можете использовать модификатор onPreviewKey и использовать LocalFocusManager для перемещения фокуса с помощью модификатора moveFocus .
В следующем примере показано поведение механизма фокуса по умолчанию: при обнаружении нажатия клавиши tab фокус перемещается на следующий элемент в списке фокуса. Хотя обычно это не то, что вам нужно настраивать, важно знать внутреннюю работу системы, чтобы иметь возможность изменить поведение по умолчанию.
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
В этом примере функция focusManager.moveFocus() перемещает фокус на указанный элемент или в направлении, указанном в параметре функции.
Рекомендуется для вас
- Примечание. Текст ссылки отображается, когда JavaScript отключен.
- Реагировать на фокусировку
- Фокус в создании
- Изменить порядок обхода фокуса