当前位置: 首页 > news >正文

【Android Compose】焦点管理

官方文档链接:

https://developer.android.google.cn/develop/ui/compose/touch-input/focus?hl=zh-cn

1、更改焦点遍历顺序

1.1、替换一维遍历顺序

(1)创建焦点引用对象:

/// 创建4个引用对象(二选一)
// 语法1:
val (first, second, third, fourth) = remember { FocusRequester.createRefs() }
// 语法2:数组
val focusRequesters = remember { List(4){ FocusRequester.createRefs() } }

(2)使用 focusRequester 修饰符将它们分别与 可组合项

Column {Row {// 注意:TextButton 具有默认焦点(可聚焦)// 如果此处是没有默认聚焦的元素,比如 Box,则需要在Modifer最后面(所有焦点函数之后)// 注意是最后面加上 .focusable()TextButton({}, Modifier.focusRequester(first/*focusRequesters[0]*/)/* .focusable()*/) { Text("First field") }TextButton({}, Modifier.focusRequester(third/*focusRequesters[1]*/)) { Text("Third field") }}Row {TextButton({}, Modifier.focusRequester(second/*focusRequesters[2]*/)) { Text("Second field") }TextButton({}, Modifier.focusRequester(fourth/*focusRequesters[3]*/)) { Text("Fourth field") }}
}

(3)使用 focusProperties 修饰符指定自定义遍历顺序
因为有些布局或组件默认会带有遍历默认顺序,比如 Column 的孩子的默认遍历顺序是从上到下。
但是有时候我们需要改变这种顺序:

// 一维:它的下一个是谁、上一个是谁
Modifier.focusRequester(first).focusProperties { next = second }..../* .focusable()*/...// 二维:上下左右
Modifier.focusRequester(fourth).focusProperties {down = thirdright = second}

2 更改焦点行为

2.1 焦点小组

LazyVerticalGrid(columns = GridCells.Fixed(4)) {item(span = { GridItemSpan(maxLineSpan) }) {// 将其设置为一个焦点组:Row(modifier = Modifier.focusGroup()) {FilterChipA()FilterChipB()FilterChipC()}}items(chocolates) {SweetsCard(sweets = it)}
}

2.2 使可组合项可聚焦

var color by remember { mutableStateOf(Green) }
Box(Modifier.background(color).onFocusChanged { color = if (it.isFocused) Blue else Green }// 注意要放在焦点相关函数的最后面,.focusable()
) {Text("Focusable 1")
}

2.3 使可组合项不可聚焦

var checked by remember { mutableStateOf(false) }Switch(checked = checked,onCheckedChange = { checked = it },// Prevent component from being focusedmodifier = Modifier// 使原本可以聚焦的,变得不可聚焦....focusProperties { canFocus = false }
)

2.4 使用 FocusRequester 手动请求焦点

上面说了可以手动添加焦点引用对象。添加这个请求对象之后,可以使用焦点请求对象手动请求焦点…

// 焦点请求对象
val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }TextField(value = text,onValueChange = { text = it },// 为这个元素添加上面的焦点请求对象(覆盖默认的焦点请求对象)modifier = Modifier.focusRequester(focusRequester)
)// 在添加焦点请求对象之后,就可以手动请求:
Button(onClick = {// 手动请求(在.focusRequester(focusRequester)之后)focusRequester.requestFocus() }) {Text("Request focus on TextField")
}

2.5 捕获和释放 焦点

调用 captureFocus() 方法,并且之后必须要用 freeFocus() 方法将其释放
// 经常用在 TextField 之类的组件
// 比如下面的示例中,首先 TextField 已经获取焦点,
// 但是我还想让它在输入长度达到 3 之后突出视觉效果,可以尝试捕获焦点…

val textField = FocusRequester()TextField(value = text,onValueChange = {text = itif (it.length > 3) {textField.captureFocus()} else {textField.freeFocus()}},modifier = Modifier.focusRequester(textField)
)

2.6 焦点修饰符的优先级

Modifier.focusProperties { right = Default }.focusProperties { right = item1 }.focusProperties { right = item2 }// 放在最后....focusable()

注意:焦点相关修饰函数顺序由决定性作用,不同的顺序会带来不同的效果:

// 作用失效案例1:
Box(Modifier.focusable() // 让焦点函数生效.focusRequester(Default)// 后声明请求器 → 无法关联.onFocusChanged {}// 当焦点发生变化的时候,回调// 上面这个案例会导致下面两个焦点修饰符函数不生效。// 因为 focusable() 的作用的让(前面已经)配置生效。// 所以先生效,再配置的逻辑顺序是不符合要求的
)
// 作用失效案例2:
Box(Modifier.onFocusChanged {}// 先监听焦点变化(无效),应该先绑定焦点.focusRequester(Default).focusable()
)// 正确顺序:
Box(Modifier.focusRequester(Default) // 先声明请求器.onFocusChanged {}       // 可放在此处(但可能不推荐).focusable()            // 后声明可聚焦 → 请求器生效
)

2.7 进入或退出时重定向焦点

什么是进入:
一般就是 Enter 键触发的行为

什么是退出:退出可组合区域,比如说焦点离开Colum1,进入Column2。
离开Column1就是退出行为。

在这里插入图片描述

比如在第一列离开,如果想要将焦点放置在第二列,可以:

val otherComposable = remember { FocusRequester() }Modifier.focusProperties {// 离开第一列的时候,我们根据离开的方向指定下一个焦点应该到达何处exit = { focusDirection ->when (focusDirection) {Right -> Cancel// 离开且按右方向键,就取消焦点Down -> otherComposable// 离开且按下键,就移动到第二列else -> Default// 其他方向使用默认}}
}

2.8 更改焦点推进方向

val focusManager = LocalFocusManager.current
var text by remember { mutableStateOf("") }TextField(value = text,onValueChange = { text = it },modifier = Modifier.onPreviewKeyEvent {when {//  检测到tab按键(并释放抬起),焦点移至焦点中的下一个元素 列表KeyEventType.KeyUp == it.type && Key.Tab == it.key -> {// 移动焦点到默认的下一个焦点元素focusManager.moveFocus(FocusDirection.Next)true}else -> false}}
)

3、回应焦点

3.1、添加视觉效果(比如颜色)

var color by remember { mutableStateOf(Color.White) }
Card(modifier = Modifier.onFocusChanged {color = if (it.isFocused) Red else White}.border(5.dp, color)
) {}

3.2、高级视觉提示

(1)首先,创建一个 IndicationInstance,以在界面中直观地绘制所需提示:

private class MyHighlightIndicationNode(private val interactionSource: InteractionSource) :Modifier.Node(), DrawModifierNode {private var isFocused = falseoverride fun onAttach() {coroutineScope.launch {var focusCount = 0interactionSource.interactions.collect { interaction ->when (interaction) {is FocusInteraction.Focus -> focusCount++is FocusInteraction.Unfocus -> focusCount--}val focused = focusCount > 0if (isFocused != focused) {isFocused = focusedinvalidateDraw()}}}}override fun ContentDrawScope.draw() {drawContent()if (isFocused) {drawRect(size = size, color = Color.White, alpha = 0.2f)}}
}

(2)接下来,创建一个 Indication 并记住聚焦状态:

object MyHighlightIndication : IndicationNodeFactory {override fun create(interactionSource: InteractionSource): DelegatableNode {return MyHighlightIndicationNode(interactionSource)}override fun hashCode(): Int = -1override fun equals(other: Any?) = other === this
}

(3)通过 indication() 修饰符将 Indication 和 InteractionSource 添加到界面中:

var interactionSource = remember { MutableInteractionSource() }Card(modifier = Modifier.clickable(interactionSource = interactionSource,indication = MyHighlightIndication,enabled = true,onClick = { })
) {Text("hello")
}

3.3、焦点状态

// 焦点一般有这三种状态,可以直接在焦点回调里面获取:
Modifier.onFocusChanged {val isFocused = it.isFocused// 只检查当前元素val hasFocus = it.hasFocus// 不仅检查当前,还检查孩子们val isCaptured= it.isCaptured//每当获得焦点时,isCaptured 都会返回 true// 一般出现这种清空都是当 TextField 包含不正确的数据时,就会尝试聚焦 其他元素不会清除焦点。
}

相关文章:

  • 使用 Truffle 和 Ganache 搭建本地以太坊开发环境并部署一个简单智能合约
  • Android学习总结之ANR问题
  • 阿里云VS AWS中国区:ICP备案全攻略与常见误区解析
  • QT对话框及其属性
  • 电机试验平台:实现精准测试与优化设计
  • 长尾词驱动SEO优化实战
  • Go context 包的底层实现原理
  • IntelliJ IDEA修改实体类成员变量的名称(引入了该实体类的全部文件也会自动更新变量的名称)
  • 基于 Nginx 的 WebSocket 反向代理实践
  • 探索 AI 在文化遗产保护中的新使命:数字化修复与传承
  • 使用css修饰网页元素
  • 认识哈希以及哈希表的模拟实现
  • Unity中文件上传以及下载,获取下载文件大小的解决方案
  • Ubuntu下安装vsode+qt搭建开发框架(一)
  • 智慧园区IOT项目与AI时代下的机遇 - Java架构师面试实战
  • 设计一个关键字统计程序:利用HashMap存储关键字统计信息,对用户输入的关键字进行个数统计。
  • P3309 [SDOI2014] 向量集 Solution
  • 浏览器界面无显示,提示“代理服务器可能有问题”,这是怎么回事呢?
  • Windows 安装 Neo4j 教程
  • 做大模型应用所需的一点点基础数学理论
  • 李勇已任内蒙古乌兰察布市委副书记,曾在中央编办任职
  • 13家券商一季报出炉:超七成业绩预喜,财通、湘财、第一创业下滑
  • 首映|马丽:真想抱抱臧姑娘,对她说辛苦了
  • 印度媒体称印巴在克什米尔再次交火
  • 三大猪企去年净利润同比均较大幅度增长,资产负债率齐降
  • 五矿地产:今年要确保债务“不爆雷”、交付“不烂尾”