3

I find listeners for onClick and onLongClick and even onPress but there is no event/listener for something like buttonDown and buttonUp, or onPress and onRelease.

Am I missing something? My current use case is that when a user presses a button I increment a count and when the user releases it I decrease the count. But in general I want something to start happening as soon as the user presses the button and stop when the user releases it. (For a real life example, see how Facebook Messenger records a video, you keep the button pressed to start and it stops when you release it.

I am using Jetpack Compose on Android.

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
user2297550
  • 3,142
  • 3
  • 28
  • 39
  • 1
    Does [this](https://stackoverflow.com/a/70718716/3585796) answer your question? It's available for modifiers like `Modifier.clickable` too. – Phil Dukhov Mar 21 '22 at 12:38

5 Answers5

10

You can use the InteractionSource.collectIsPressedAsState to know if the Button is pressed. You can add a side effect to know when the Button is released.

Something like:

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()

var currentStateTxt by remember { mutableStateOf("Not Pressed") }
var currentCount by remember { mutableStateOf(0) }

if (isPressed){
    //Pressed
    currentStateTxt = "Pressed"
    currentCount += 1

    //Use if + DisposableEffect to wait for the press action is completed
    DisposableEffect(Unit) {
        onDispose {
            //released
            currentStateTxt = "Released"
        }
    }
}

Button(onClick={},
    interactionSource = interactionSource
){
        Text("Current state = $currentStateTxt")
        Text("Count = $currentCount")
}

enter image description here

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • So I'm curious -- why can't I more simply use an `else` to detect that the press action is completed? What is the benefit of using a `DisposableEffect` instead? – user2297550 Mar 30 '22 at 15:07
  • 1
    To answer my own question, an `else` block could be potentially executed many times if there were a recomposition for any reason, including reasons unrelated to the button press/release. A `DispsableEffect` would be executed exactly once. THAT leads me to FIX THE BUG in this answer. The `//Pressed` code should not be free-standing in the `if` block. Instead, it should be moved into a `LaunchedEffect`, which is the counterpart of `DisposableEffect`. Then it would be executed exactly once for every button press rather than for every recomposition, avoiding the racing counter shown in the pic!! – user2297550 Mar 30 '22 at 15:18
3
Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = {
            //start
            val released = try {
                tryAwaitRelease()
            } catch (c: CancellationException) {
                false
            }
            if (released) {
                //ACTION_UP
            } else {
                //CANCELED
            }
        },
        onTap = {
            // onTap
        },
        onDoubleTap = {
            //onDoubleTap
        },
        onLongPress = {
            //onLongPress
        }
    )
}
Yshh
  • 654
  • 1
  • 10
2

Use .pointerInput modifier:

.pointerInput(Unit) {
            forEachGesture {
                awaitPointerEventScope {
                    awaitFirstDown()
                    //onPress actions here
                    do {
                        val event = awaitPointerEvent()
                        //Track other pointer evenst, like Drag etc...
                    } while (event.changes.any { it.pressed })
                    //onRelease actions here
                }
            }
        }
bylazy
  • 1,055
  • 1
  • 5
  • 16
1

these codes may be helpful for you

var isPressed by remember {
    mutableStateOf(false)
}

    .pointerInput(Unit) {
                detectTapGestures(
                    onPress = {
                        try {
                            isPressed = true

                                isPlaying = true
                                sampleSong.start()



                            awaitRelease()
                        }
                        finally {
                            isPressed = false
                            isPlaying = false
                            sampleSong.pause()


                        }
-1

I think you use Touch Listener on button , its easily detect button touch or untouch example

override fun onTouchEvent(e: MotionEvent): Boolean {
val x: Float = e.x
val y: Float = e.y

when (e.action) {
    MotionEvent.ACTION_MOVE -> {

        var dx: Float = x - previousX
        var dy: Float = y - previousY

        // reverse direction of rotation above the mid-line
        if (y > height / 2) {
            dx *= -1
        }

        // reverse direction of rotation to left of the mid-line
        if (x < width / 2) {
            dy *= -1
        }

        renderer.angle += (dx + dy) * TOUCH_SCALE_FACTOR
        requestRender()
    }
}

previousX = x
previousY = y
return true

}

More info of Touch Listener in this link Android Touch Listener