首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
处理用户输入
📅 2026/10/5 16:18:56
✍️ 爱科研究院
👁 阅读 3,247
看下面的代码ComposablefunPointerInputEvent(){Box(modifierModifier.requiredSize(200.dp).background(Color.Blue).pointerInput(Unit){awaitPointerEventScope{while(true){valeventawaitPointerEvent()Log.d(PointerInputEvent,event:${event.type})}}})}当你在 Box 所在的蓝色区域内按下拖动然后抬起鼠标会收到如下打印2026-04-19 21:49:34.607 26743-26743 PointerInputEvent com.example.composetest D event: Press 2026-04-19 21:49:34.772 26743-26743 PointerInputEvent com.example.composetest D event: Move 2026-04-19 21:49:35.023 26743-26743 PointerInputEvent com.example.composetest D event: Move 2026-04-19 21:49:35.286 26743-26743 PointerInputEvent com.example.composetest D event: Release这里封装了一个 Box在 Box 里面调用了 Modifier.pointerInput代码如下funModifier.pointerInput(key1:Any?,block:PointerInputEventHandler):ModifierthisthenSuspendPointerInputElement(key1key1,pointerInputEventHandlerblock)pointerInput 函数的作用是在当前组件区域内处理 Pointer Input指针输入事件它可以传入一个 key1 参数当 key1 改变时block 会被取消并重新执行重新开始监听。block 是一个 PointerInputEventHandler PointerInputEventHandler 代码如下funinterfacePointerInputEventHandler{// ↑ 函数式接口只有一个抽象方法可以直接用 lambda 简写suspendoperatorfunPointerInputScope.invoke()// ↑ ↑ ↑ ↑ ↑// 挂起函数 运算符重载 扩展函数 接收者类型 方法名}它是一个函数式接口也称为 SAM 接口即 Single Abstract Method函数式接口是只有一个抽象方法的接口可以使用 Lambda 表达式来实例化它。Modifier.pointerInput(Unit){...}// lambda 写法invoke 是 kotlin 内置 “魔法函数名”任何类或接口只要实现了 operator fun invoke()它的实例就能直接当函数用。比如原来你要这样写// 定义valhandlerPointerInputEventHandler{...}// 使用handler.invoke()现在只需要这样写就可以了// 定义valhandlerPointerInputEventHandler{...}// 使用handler()PointerInputScope.invoke() 是扩展函数接收者意思是 lambda 内部可以直接访问 PointerInputScope 的成员Modifier.pointerInput(Unit){// 这里的 this 是 PointerInputScope可以直接用它的成员size// 组件尺寸awaitPointerEvent()// 等待指针事件awaitFirstDown()// 等待按下currentEvent// 当前事件}PointerInputScope 代码如下interfacePointerInputScope:Density{valsize:IntSizevaldepth:Int...suspendfunRawaitPointerEventScope(block:suspendAwaitPointerEventScope.()-R):R}这里 awaitPointerEventScope 会挂起并等待指针事件。同一个 PointerInputScope 里可以用 launch 并发跑多个 awaitPointerEventScope:Modifier.pointerInput(Unit){launch{awaitPointerEventScope{while(true){valeventawaitPointerEvent()// 处理拖动}}}launch{awaitPointerEventScope{while(true){valeventawaitPointerEvent()// 处理点击}}}}多个 block 按安装的顺序进行事件派发。awaitPointerEvent 也是一个挂起函数代码如下suspend fun awaitPointerEvent(pass: PointerEventPass PointerEventPass.Main): PointerEvent它会挂起直到一个 PointerEvent 被上报给 pass阶段pass 默认是 PointerEventPass.Main什么是 PointerEventPass三个阶段一个指针事件在组件树里会按三个 pass 依次传递Pass方向含义Initial祖先 → 后代自顶向下最外层先看到Main后代 → 祖先自底向上最内层先处理默认Final后代 → 祖先自底向上只看未被消费的事件pass 参数可以用于选你在哪个阶段接收事件。在等下一个事件之前你可以修改返回的 PointerEvent 来 “消费” 事件while(true){valeventawaitPointerEvent()event.changes.forEach{change-if(change.pressed){change.consume()// ← 消费标记事件已被处理}}// 消费后下一阶段Final pass的其他 handler 就收不到这个事件了}消费后下一阶段Final pass的其他 handler 就收不到这个事件了。消费consume机制图解事件到达 │ ▼ Initial pass祖先先看 │ ▼ Main pass后代处理默认这里 │ ← 你在这里 change.consume() ▼ Final pass只收到未消费的事件 │ ← 被消费后这里收不到 ▼ 结束经典用法Modifier.pointerInput(Unit){awaitPointerEventScope{while(true){valeventawaitPointerEvent()// 等事件默认 Mainevent.changes.forEach{change-if(change.pressed){change.consume()// 消费防止泄漏给父组件}}}}}使控件可点击、滚动、拖拽使用 pointerInput 函数来处理用户输入是比较偏底层的就像是在 View 系统中处理 TouchEvent 一样。事实上我们很多时候并不需要使用这么底层的 API。Modifier 提供了很多的上层 API 来处理诸如点击、滚动、拖拽等用户输入事件。首先看点击。事实上有些控件默认就是可以点击的如 Button。而有些则不能如 Box。让一个默认不能点击的控件变得可以点击并不一定非要使用 pointerInput 函数clickable 函数也能做到并且代码会更加简洁。ComposablefunHighLevelCompose(){valcontextLocalContext.currentBox(modifierModifier.requiredSize(200.dp).background(Color.Blue).clickable{Toast.makeText(context,Box is clicked,Toast.LENGTH_SHORT).show()})}再来看拖拽。draggable 函数允许让一个控件在水平或垂直方向上拖拽并可以监听用户的拖拽距离我们再根据返回的拖拽距离对控件进行相应的偏移就可以实现拖拽效果了。ComposablefunHighLevelCompose(){varoffsetXbyremember{mutableStateOf(0f)}Box(modifierModifier.offset{IntOffset(offsetX.roundToInt(),0)}.requiredSize(200.dp).background(Color.Blue).draggable(orientationOrientation.Horizontal,staterememberDraggableState{delta-offsetXdelta}))}不过 draggable 函数有一个弊端它只能允许控件在水平或垂直方向上拖拽不可以同时在水平和垂直方向上拖拽。所以如果你有这种特殊需求的话那么就可以使用更加底层的 pointerInput 函数来实现ComposablefunHighLevelCompose(){varoffsetXbyremember{mutableStateOf(0f)}varoffsetYbyremember{mutableStateOf(0f)}Box(modifierModifier.offset{IntOffset(offsetX.roundToInt(),offsetY.roundToInt())}.requiredSize(200.dp).background(Color.Blue).pointerInput(Unit){detectDragGestures{change,dragAmount-change.consume()offsetXdragAmount.x offsetYdragAmount.y}})}在 pointerInput 函数内部我们调用 detectDragGestures 来监听用户的拖拽手势这样就可以同步获得用户在水平和垂直方向上的拖拽距离并对控件进行相应的偏移了。另外要记得由于这是底层API所以很多事情要自己做比如事件处理完了要记得调用 consume 函数将它消费掉。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/5 16:18:56
轮毂缺陷像素分割实战:基于U-Net的工业质检方案与训练部署全解析
2026/10/5 16:18:56
ESP32-CAM开发实战:环境搭建、接线与手机远程监控指南
2026/10/5 16:18:56
DeepSeek桌面版实战指南:Electron架构下的本地大模型工作流
2026/10/5 16:53:58
构建 Ansible 清单
2026/10/5 16:53:58
【老计带你懂AI算法】22:模型选型速查与怎么判断模型好不好,系列收官
2026/10/5 16:53:58
Linux网络(二十):TCP拥塞控制与延迟应答详解:从拥塞窗口到TCP性能优化,理解TCP与UDP的效率差异
2026/10/5 16:53:58
云服务器nacos搭建-单机
2026/10/5 16:53:58
Cursor/VS Code 左侧资源管理器字体放大:TaoToken 场景下的界面可读性调优大纲
2026/10/5 16:48:57
macOS 下使用 Docker 部署运行 GameAISDK 与 SDKTool 完整指南(含 socat / XQuartz / adbkit 环境搭建)
2026/10/5 0:02:57
AZ-104题库深度拆解:从刷题到掌握Azure管理员核心考点
2026/10/5 0:02:57
WorkBuddy:基于MCP协议的组织级工作流神经中枢
2026/10/5 0:02:57
大模型 / AI 应用常见面试题及答案汇总(2026 最新版):用 TaoToken 统一 Key 跑通高频考点代码验证
2026/10/5 4:43:56
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/5 1:10:25
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/5 13:05:37
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/4 2:41:08
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/4 17:59:15
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/3 15:20:14
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)