资讯详情

资讯详情

处理用户输入

看下面的代码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 函数将它消费掉。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →