You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现长按连续增减按钮(点击±1,长按每0.5秒±10)

Jetpack Compose实现按钮单击±1、长按连续±10的功能

需求描述

我需要修改现有按钮的交互逻辑:

  • 单击按钮时,执行±1的增减操作;
  • 长按按钮时,每0.5秒执行一次±10的增减操作,直到用户松开按钮。

当前代码如下:

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Transparent)
        .weight(2f)
        .combinedClickable(
            onClick = {
                when (selectedCounter) {
                    "Life" -> if (life > 0) life -= 1
                    "Poison" -> if (poison > 0) poison -= 1
                }
                consecutiveTapsRemove = maxOf(0, consecutiveTapsRemove + 1)
            },
            onLongClick = {
                when (selectedCounter) {
                    "Life" -> if (life > 0) life -= 10
                    "Poison" -> if (poison > 0) poison -= 10
                }
                consecutiveTapsRemove = maxOf(0, consecutiveTapsRemove + 10)
            }
        ),
) {}

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Transparent)
        .weight(2f)
        .combinedClickable(
            onClick = {
                when (selectedCounter) {
                    "Life" -> life += 1
                    "Poison" -> poison += 1
                }
                consecutiveTapsAdd = maxOf(0, consecutiveTapsAdd + 1)
            },
            onLongClick = {
                when (selectedCounter) {
                    "Life" -> life += 10
                    "Poison" -> poison += 10
                }
                consecutiveTapsAdd = maxOf(0, consecutiveTapsAdd + 10)
            }
        )
) {}

实现方案

combinedClickable的onLongClick仅触发一次,无法满足连续执行需求。需要通过pointerInput监听触摸事件,结合协程定时执行增减逻辑,具体实现如下:

1. 封装通用增减逻辑

先封装统一的增减函数,减少代码重复并处理边界值:

// isIncrement:true为加操作,false为减操作
// value:每次增减的数值
// setLife/setPoison:状态更新回调
private fun modifyCounter(
    isIncrement: Boolean,
    value: Int,
    setLife: (Int) -> Unit,
    setPoison: (Int) -> Unit
) {
    val delta = if (isIncrement) value else -value
    when (selectedCounter) {
        "Life" -> {
            val newLife = if (isIncrement) life + delta else maxOf(0, life + delta)
            setLife(newLife)
        }
        "Poison" -> {
            val newPoison = if (isIncrement) poison + delta else maxOf(0, poison + delta)
            setPoison(newPoison)
        }
    }
}

2. 重写按钮触摸逻辑

用pointerInput替代combinedClickable,实现完整的触摸交互:

// 减号按钮
Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Transparent)
        .weight(2f)
        .pointerInput(Unit) {
            coroutineScope {
                while (true) {
                    awaitPointerEventScope {
                        // 等待手指按下
                        val downEvent = awaitFirstDown()
                        // 执行单击减1逻辑
                        modifyCounter(false, 1, { life = it }, { poison = it })
                        consecutiveTapsRemove = maxOf(0, consecutiveTapsRemove + 1)

                        // 等待300ms判定是否长按(系统默认长按阈值,可调整)
                        val isLongPress = withTimeoutOrNull(300) {
                            awaitPointerEvent()
                        } == null

                        if (isLongPress) {
                            launch {
                                // 循环执行减10,直到手指松开
                                while (downEvent.pressed) {
                                    modifyCounter(false, 10, { life = it }, { poison = it })
                                    consecutiveTapsRemove = maxOf(0, consecutiveTapsRemove + 10)
                                    delay(500) // 每0.5秒执行一次
                                    // 更新触摸状态,确认是否仍按下
                                    awaitPointerEvent()
                                }
                            }
                        }
                        // 等待手指抬起,结束本次交互
                        awaitRelease()
                    }
                }
            }
        }
) {}

// 加号按钮
Box(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Transparent)
        .weight(2f)
        .pointerInput(Unit) {
            coroutineScope {
                while (true) {
                    awaitPointerEventScope {
                        val downEvent = awaitFirstDown()
                        // 执行单击加1逻辑
                        modifyCounter(true, 1, { life = it }, { poison = it })
                        consecutiveTapsAdd = maxOf(0, consecutiveTapsAdd + 1)

                        val isLongPress = withTimeoutOrNull(300) {
                            awaitPointerEvent()
                        } == null

                        if (isLongPress) {
                            launch {
                                while (downEvent.pressed) {
                                    modifyCounter(true, 10, { life = it }, { poison = it })
                                    consecutiveTapsAdd = maxOf(0, consecutiveTapsAdd + 10)
                                    delay(500)
                                    awaitPointerEvent()
                                }
                            }
                        }
                        awaitRelease()
                    }
                }
            }
        }
) {}

关键说明

  • pointerInput是Compose中处理精细触摸事件的核心API,能完整监听按下、移动、抬起全生命周期;
  • 长按判定时间设为300ms(系统默认值),可根据需求调整;
  • 协程循环中通过delay(500)控制执行间隔,每次循环更新触摸状态确保手指松开时立即停止;
  • 统一的modifyCounter函数处理了数值边界(避免life/poison小于0),减少重复代码。

内容的提问来源于stack exchange,提问作者Bjurhager

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 09:57:03