【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 包含不正确的数据时,就会尝试聚焦 其他元素不会清除焦点。
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78504.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dwj2025426

目录 一、25. K 个一组翻转链表 - 力扣(LeetCode) 二、 215. 数组中的第K个最大元素 - 力扣(LeetCode) 三、 15. 三数之和 - 力扣(LeetCode) 一、25. K 个一组翻转链表 - 力扣(LeetCode&#…

C++ std::forward 详解

在 C 11 引入的众多特性中,std::forward占据着独特且重要的地位。它主要用于实现所谓的 “完美转发”,这一机制在现代 C 编程中发挥着关键作用,尤其是在编写通用库和高效代码时。 什么是完美转发? 完美转发是指在函数模板中&…

如何保证线程安全(含典型手段与应用场景)

✨ 1. 什么是线程安全? 线程安全指的是:当多个线程同时访问同一块代码时,无论运行时环境采用怎样的调度方式或者这些线程将怎样交替执行,代码的行为都能正确执行,且不会出现数据不一致、脏数据或异常崩溃。 举个简单…

Qt/C++开发监控GB28181系统/协议解释说明/SIP内容解释/每一行数据什么含义

一、前言 搞gb28181开发,首要任务就是解析协议,按照gb28181的文档来,还是非常详细的,通过抓包工具可以查看到具体的收发数据,也可以打开网络调试助手工具,监听5060端口,看到上报的数据&#xf…

C++:string 1

练习题&#xff1a; 这个题的思路是从前往后&#xff0c;从后往前同时找&#xff0c;不是字母的话就继续&#xff0c;是的话就交换。 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <string> using namespace std; //1、4个…

SMT贴片加工费控制与优化实践指南

内容概要 SMT贴片加工费的控制与优化需建立在对成本结构的系统性认知基础上。本节从物料采购、设备运行、工艺参数三大维度切入&#xff0c;结合BOM清单管理、钢网使用规范等实操环节&#xff0c;构建覆盖全流程的降本增效框架。以下表格列举了SMT加工成本的典型构成要素及其占…

未来医院已来:AI如何实现无死角安全监控

AI智慧医院如何用算法守护安全与效率 ## 背景&#xff1a;医疗场景的智能化转型需求 现代医院作为人员密集、场景复杂的公共场所&#xff0c;面临诸多管理痛点&#xff1a;患者跌倒可能延误救治、医闹事件威胁安全、医疗垃圾处置不当引发感染风险、重点区域&#xff08;如药…

Nuxt3中使用UnoCSS指南

Nuxt3中使用UnoCSS指南 UnoCSS是一个高度可定制的、原子化CSS引擎&#xff0c;可以轻松集成到Nuxt3项目中。下面介绍如何在Nuxt3中安装和配置UnoCSS。 安装步骤 安装UnoCSS的Nuxt模块&#xff1a; # 使用pnpm pnpm add -D unocss unocss/nuxt# 使用yarn yarn add -D unocss…

mmap详解

mmap详解 mmap基础概念mmap内存映射原理mmap相关函数调用mmap的使用细节mmap和常规文件操作的区别 mmap基础概念 mmap是一种内存映射文件的方法&#xff0c;即将一个文件或者其它对象映射到进程的地址空间&#xff0c;实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一…

Vue3的内置组件 -实现过渡动画 TransitionGroup

Vue3的内置组件 -实现过渡动画 TransitionGroup 是一个内置组件&#xff0c;用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果 支持和 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器&#xff0c;但有以下几点区别&#xff1a; 默认情况下&…

【软考-架构】14、软件可靠性基础

✨资料&文章更新✨ GitHub地址&#xff1a;https://github.com/tyronczt/system_architect 文章目录 软件可靠性基本概念软件可靠性建模软件可靠性管理软件可靠性设计N版本程序设计恢复块设计&#xff08;动态冗余&#xff09;双机容错技术、集群技术负载均衡软件可靠性测试…

使用Python+OpenCV对视频抽帧保存为JPG图像

使用PythonOpenCV对视频抽帧保存为JPG图像 import os import cv2 import time#视频文件夹路径&#xff0c;可修改 videoPath D:\\video\\ #保存的图片文件夹路径&#xff0c;可修改 savePath D:\\images\\ videolist os.listdir(videoPath) if not os.path.exists(savePath…

学习整理在centos7上安装mysql8.0版本教程

学习整理在centos7上安装mysql8.0版本教程 查看linux系统版本下载mysql数据库安装环境检查解压mysql安装包创建MySQL需要的目录及授权新增用户组新增组用户配置mysql环境变量编写MySQL配置文件初始化数据库初始化msyql服务启动mysql修改初始化密码配置Linux 系统服务工具,使My…

DeepSeek预训练追求极致的训练效率的做法

DeepSeek在预训练阶段通过多种技术手段实现了极致的训练效率,其中包括采用FP8混合精度训练框架以降低计算和内存需求 ,创新性地引入Multi-head Latent Attention(MLA)压缩KV缓存以提升推理效率,以及基于Mixture-of-Experts(MoE)的稀疏计算架构以在保证性能的同时显著降低…

【计算机视觉】CV项目实战- 深度解析TorchVision_Maskrcnn:基于PyTorch的实例分割实战指南

深度解析TorchVision_Maskrcnn&#xff1a;基于PyTorch的实例分割实战指南 技术背景与核心原理Mask R-CNN架构解析项目特点 完整实战流程环境准备硬件要求软件依赖 数据准备与标注1. 图像采集2. 数据标注3. 数据格式转换 模型构建与训练1. 模型初始化2. 数据加载器配置3. 训练优…

x86系列CPU寄存器和汇编指令总结

文章目录 概要一、寄存器1.1、8086寄存器1.2、通用寄存器1.3、扩展寄存器 二、指令集三、x86指令集常见指令使用说明四、汇编4.1、汇编语法4.2、nsam汇编 五、参考 概要 在对学习Go的过程中&#xff0c;涉及到了汇编&#xff0c;因此对X86系列CPU的背景、寄存器、汇编指令做了一…

戴维斯双击选股公式如何编写?

戴维斯双击&#xff0c;指的是营收增长和净利润增长同步&#xff0c;并有超预期的财务状况。 戴维斯双击是指在低市盈率&#xff08;P/E&#xff09;时买入股票&#xff0c;待公司盈利增长和市盈率提升后卖出&#xff0c;以获取双重收益。以下是一个简单的通达信选股模型示例&…

前端面试宝典---vue原理

vue的Observer简化版 class Observer {constructor(value) {if (!value || typeof value ! object) returnthis.walk(value) // 对对象的所有属性进行遍历并定义响应式}walk (obj) {Object.keys(obj).forEach(key > defineReactive(obj, key, obj[key]))} } // 定义核心方法…

从“聋哑设备“到超级工厂:EtherCAT转Modbus协议网关正在重构工业未来

当全球工厂加速迈向工业4.0&#xff0c;您的生产线是否因Modbus设备“拖后腿”而被迫降速&#xff1f;无需百万改造&#xff01;无需淘汰设备&#xff01;一套EtherCAT从站转Modbus协议网关&#xff0c;让30年老机床与智能工厂实时对话&#xff0c;效率飙升300%&#xff01; 一…

Tauri文件系统操作:桌面应用的核心能力(入门系列四)

今天我们来聊聊Tauri中一个超级重要的功能 - 文件系统操作。这可是Web应用和桌面应用最大的区别之一。在浏览器里&#xff0c;出于安全考虑&#xff0c;我们对文件系统的访问被限制得死死的。但在Tauri桌面应用中&#xff0c;我们可以安全地访问用户的文件系统&#xff0c;这简…