Home > Enterprise >  How to detect motion events on a compose button?
How to detect motion events on a compose button?

Time:10-17

I am trying to create an icon button which invokes a lambda when tapped, but if the user presses the button and holds it, then also the lambda should get continuously invoked in fixed intervals.

@Composable
fun MyIconButton(
    someLambda: () -> Unit
) {
    IconButton(onClick = someLambda) {
        Icon(
            // painter and content description
        )
    }
}

Here what I want is that when user presses the button, someLambda should get invoked (which is working fine). Additionally, I also want to invoke someLambda repeatedly (with a gap of 500ms between two invocations) until the user releases the button.

Basically what I want is to detect something like the KeyUp and KeyDown events.

How to achieve this?

CodePudding user response:

You can use Modifier.pointerInpteropFilter for this:

var job by remember {
    mutableStateOf<Job?>(null)
}
val scope = rememberCoroutineScope()

Icon(
    imageVector = Icons.Filled.Favorite,
    modifier = Modifier
        .requiredSize(96.dp)
        .pointerInteropFilter {
            when (it.action) {
                MotionEvent.ACTION_DOWN -> {
                    job = scope.launch {
                        while (true) {
                            // trigger event
                            Log.d("foo", "Trigger event")
                            delay(500L)
                        }
                    }
                }
                MotionEvent.ACTION_UP,
                MotionEvent.ACTION_CANCEL -> {
                    job?.cancel()
                    job = null
                }
            }
            true
        },
    contentDescription = "Sample icon"
)

Another solution is to use Modifier.pointerInput:

val scope = rememberCoroutineScope()
Icon(
    imageVector = Icons.Filled.Favorite,
    modifier = Modifier
        .requiredSize(96.dp)
        .pointerInput(Unit) {
            while (true) {
                awaitPointerEventScope {
                    awaitFirstDown()
                    val job = scope.launch {
                        while (true) {
                            // trigger event
                            Log.d("foo", "Trigger event")
                            delay(500L)
                            Log.d("foo", "After delay")
                        }
                    }
                    waitForUpOrCancellation()
                    job.cancel()
                }
            }
        },
    contentDescription = "Sample icon"
)

CodePudding user response:

I'll update the answer by tomorrow, but for now, this should suit your use case

Modifier.pointerInput(Unit) {
    detectTapGestures(
        onPress = { /* Called when the gesture starts */ },
        onDoubleTap = { /* Called on Double Tap */ },
        onLongPress = { /* Called on Long Press */ },
        onTap = { /* Called on Tap */ }
    )
}

CodePudding user response:

I solved the problem using this Modifier.pointerInput on my Icon.

Modifier.pointerInput(true) {
    detectTapGestures(onPress = {
        coroutineScope {
            val job = launch {
                while (true) {
                    // Invoke lambda
                    delay(500)
                }
            }
            tryAwaitRelease()
            job.cancel()
        }
    })
}

As per the documentation,

onPress is called when the press is detected and PressGestureScope.tryAwaitRelease can be used to detect when pointers have released or the gesture was canceled.

  • Related