JetPack Compose: Adding click duration

1.1k Views Asked by At

I have a composable component like so:

Card(
    modifier = Modifier
        .fillMaxWidth()
        .then(modifier ?: Modifier),
    backgroundColor = colorResource(id = R.color.Red),
    shape = RoundedCornerShape(percent = 50),
) {
    Row (
        modifier = Modifier
            .size(160.dp)
    ) {
    }
}

When the user clicks and holds the Card I want to check if the user has held the Card for a second. If they held it longer than a second then I want to log "CLICKED" but if they let go before a second then don't log "CLICKED"

How can I achieve this?

3

There are 3 best solutions below

4
On BEST ANSWER

This can be done by writing a custom gesture Modifier such as

fun Modifier.timedClick(
    timeInMillis: Long,
    interactionSource: MutableInteractionSource = remember {MutableInteractionSource()},
    onClick: (Boolean) -> Unit
) = composed {

    var timeOfTouch = -1L
    LaunchedEffect(key1 = timeInMillis, key2 = interactionSource) {
        interactionSource.interactions
            .onEach { interaction: Interaction ->
                when (interaction) {
                    is PressInteraction.Press -> {
                        timeOfTouch = System.currentTimeMillis()
                    }
                    is PressInteraction.Release -> {
                        val currentTime = System.currentTimeMillis()
                        onClick(currentTime - timeOfTouch > timeInMillis)
                    }
                    is PressInteraction.Cancel -> {
                        onClick(false)
                    }
                }

            }
            .launchIn(this)
    }

    Modifier.clickable(
        interactionSource = interactionSource,
        indication = rememberRipple(),
        onClick = {}
    )
}

Usage

val context = LocalContext.current

Card(
    shape = RoundedCornerShape(percent = 50),
) {
    Box(
        modifier = Modifier
            .timedClick(
                timeInMillis = 1000,
            ) { passed: Boolean ->
                Toast
                    .makeText(
                        context,
                        "Pressed longer than 1000 $passed",
                        Toast.LENGTH_SHORT
                    )
                    .show()
            }
            .fillMaxWidth()
            .height(100.dp),
        contentAlignment = Alignment.Center
    ) {

        Text("Hello World")
    }
}

Result

enter image description here

0
On
0
On

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

Something like:

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
var diffTime by remember { mutableStateOf(0L) }

if (isPressed){
    //Pressed
    val dateNow = Calendar.getInstance().timeInMillis
   
    DisposableEffect(Unit) {
        onDispose {
            //released
            val dateReleased = Calendar.getInstance().timeInMillis
            diffTime = dateReleased - dateNow
           
            //add your logic...
        }
    }
}

Card(
    onClick={},
    interactionSource = interactionSource,
){
    //..
}