有时,您需要覆盖元素的默认焦点行为 。例如,您可能想要对可组合项进行分组,从而防止 将焦点置于某个可组合项上,明确请求将焦点置于某个可组合项上, 捕获或释放焦点,或者在进入或退出时重定向焦点。本次 部分介绍了如何在默认操作并非默认操作时更改焦点行为 。
通过焦点小组提供连贯一致的导航
有时,Jetpack Compose 不会立即猜测正确的下一项
标签页式导航,尤其是在复杂的父级 Composables
(例如标签页和
清单
虽然焦点搜索通常遵循 Composables
的声明顺序,
但在某些情况下,这是不可行的,例如,当Composables
是不完全可见的水平可滚动项。显示位置
请参阅下面的示例。
Jetpack Compose 可能会决定聚焦于最接近 而不是继续沿途的 单向导航:
在本例中,很明显,开发者并未打算 请从巧克力标签跳转到以下第一张图片,然后返回到 糕点标签页。他们希望将重点放在标签页上, 最后一个标签页,然后重点关注内部内容:
在必须让一组可组合项获得焦点的情况下
就像上一个示例中的 Tab 行一样,您需要将
具有 focusGroup()
修饰符的父项中的 Composable
:
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
之后立即获得焦点,因为
满足您的需求
使可组合项可聚焦
某些可组合项在设计上可聚焦,例如 Button 或采用
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) )
您可以调用 FocusRequester 的 requestFocus
方法来发送实际的焦点请求。您应该在 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 = 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()
在本例中,将 item2
指示为右侧焦点的 focusProperties
将
因为它包含在前一个网址中;因此,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() ) |
进入或退出时重定向焦点
有时,您需要提供一种非常具体的导航栏,比如 如以下动画所示:
在我们深入探究如何创建之前,请务必先了解默认
焦点搜索行为无需任何修改,焦点搜索
按方向键上的 DOWN
(或等效键)可看到 Clickable 3
项
箭头键)可将焦点移至 Column
下方显示的任何内容,
退出群组并忽略右边的群组如果没有
有可聚焦项可用,焦点不会移到任何位置,但始终位于
Clickable 3
。
若要改变这种行为并提供预期的导航,您可以利用
focusProperties
修饰符,可帮助您管理聚焦时会发生什么情况
进入或退出 Composable
时:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
每当进入特定的 Composable
时,都可以将焦点定向到它
或退出层次结构的特定部分 - 例如,当您的界面有两个
因此,您需要确保每当系统处理第一列时
焦点切换到第二个:
在此 GIF 中,当焦点到达 Column
中的 Clickable 3 Composable
后,即 1,
获得焦点的下一个项目是另一个 Column
中的 Clickable 4
。此行为
可以通过将 focusDirection
与 enter
和 exit
结合使用来实现
focusProperties
修饰符内的值。它们都需要一个 lambda,
作为参数,表示焦点的来源方向,并返回
FocusRequester
。此 lambda 有三种不同的行为方式:返回
FocusRequester.Cancel
会使焦点停止继续,而
FocusRequester.Default
不会改变其行为。而是提供
附加到另一个 Composable
的 FocusRequester
会使焦点跳至该位置
特定的 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 处于关闭状态时,系统会显示链接文字
- 以应对焦点
- Compose 中的焦点
- 更改焦点遍历顺序