Jetpack Compose中如何实现悬停/拖拽经过行内项时的缩放效果
Jetpack Compose 实现Row内表情悬停/拖拽缩放方案
核心实现不要给每个子项单独加事件监听,直接在父容器统一处理指针位置,结合子项位置信息计算命中状态,流畅度比子项单独监听好很多,不会出现快速拖拽时事件丢失的问题。
核心实现步骤
第一步:记录所有表情项的位置边界
给每个表情项添加onGloballyPositioned修饰符,将每个项相对于父Row(或者Popup根容器)的边界矩形Rect存到状态列表里,注意坐标要统一转换到和指针事件相同的坐标系下,避免位置判断错位。val emojiRectList = remember { mutableStateListOf<Rect>() } // 单个表情项的修饰符 Modifier.onGloballyPositioned { coordinates -> val rect = coordinates.boundsInParent() if (index < emojiRectList.size) emojiRectList[index] = rect else emojiRectList.add(rect) }第二步:统一监听父容器的指针位置
给包裹所有表情的Row添加pointerInput修饰符,在指针事件协程里同时监听悬停、拖拽移动、指针移出三类事件,实时更新当前指针相对于父容器的坐标,指针移出父容器时将坐标置空,重置所有表情状态。val currentPointerOffset = remember { mutableStateOf<Offset?>(null) } Modifier.pointerInput(Unit) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() val change = event.changes.firstOrNull() ?: continue when (event.type) { PointerEventType.Move, PointerEventType.Hover -> { currentPointerOffset.value = change.position change.consume() } PointerEventType.Exit, PointerEventType.Release -> { currentPointerOffset.value = null } } } } }第三步:按距离计算每个表情的缩放值,加动画过渡
不要只给命中的表情加缩放,参考目标交互的逻辑,根据指针和每个表情中心的距离做递进缩放:离指针越近缩放比例越大,相邻表情也有轻微放大,过渡更自然。缩放用graphicsLayer实现,配合弹性动画避免生硬。// 单个表情的缩放计算 val targetScale = if (currentPointerOffset.value == null) 1f else { val pointerPos = currentPointerOffset.value!! val itemCenter = emojiRectList[index].center val distance = sqrt( (itemCenter.x - pointerPos.x).pow(2) + (itemCenter.y - pointerPos.y).pow(2) ) when { distance < 36.dp.toPx() -> 1.8f // 悬停命中项最大缩放 distance < 72.dp.toPx() -> 1.3f // 相邻次近项缩放 distance < 108.dp.toPx() -> 1.1f // 边缘轻微缩放 else -> 1f // 其他项保持原大小 } } val animatedScale by animateFloatAsState( targetValue = targetScale, animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy), label = "emojiScale" ) // 应用缩放到表情 Modifier.graphicsLayer { scaleX = animatedScale scaleY = animatedScale }
注意事项
- 如果用Popup承载表情面板,记录子项位置时要注意坐标系转换,
boundsInWindow()拿到的是全局窗口坐标,需要减去Popup自身的窗口偏移量,再和指针坐标做比对 - 不要给单个表情项单独设置
hoverable或draggable监听,快速滑动时事件会在子项间切换断层,父容器统一监听的方案流畅度高一个量级 - 如果需要实现选中逻辑,加个超时判断即可:指针在某个表情的Rect范围内停留超过100ms,就标记该表情为当前选中项,触发对应回调
内容的提问来源于stack exchange,提问作者Jalson1982
相关产品推荐
相关产品推荐

