android-jetpack-composetouch-eventmodifier

How to 'click' on multiple elements with one drag gesture in Jetpack Compose?


I have a composable called SequencerGrid which contains a row of composables called ToggleStep. Each ToggleStep is effectively a custom button; a Box with a .clickable modifier. When each ToggleStep is clicked on, it gets 'highlighted', changing colour (as well as changing some message in the viewModel). I want the user to be able to highlight multiple ToggleSteps with a single drag gesture. That is, when any given ToggleStep receives a PressInteraction.Press, and before that PressInteraction is released, every other ToggleStep should 'listen' to see if the position of the press crosses over itself, and execute clickToggleStep() if it does.

I guess I'm not sure quite how low-level I need to go to achieve this. I thought it seemed a good idea for all the ToggleSteps to share an interactionSource, hence I create this inside SequencerGrid() and pass it down.

Here is a truncated sample of my code, to illustrate. I have annotated two different approaches I have considered, to handle pointer input (both are incomplete!):

@Composable
fun ToggleStep(
   padWidth: Int,
   interactionSource: MutableInteractionSource,
   anyStepPressedOrDragged: State<Boolean>
){

   Box(
           modifier = Modifier
               .size(padWidth.dp)
               .drawToggleStep{ toggleStepState.value } //custom modifier; draws background

               // *** OPTION 1 ***
               .pointerInput(Unit) {
                   awaitPointerEventScope {
                       while (true) {
                           val event = awaitPointerEvent()
                           if (event.type == PointerEventType.Press) {
                               clickToggleStep()
                           }
                           if (anyStepPressedOrDragged.value && event.type == PointerEventType.Enter){
                               clickToggleStep()
                           }
                       }
                   }
               }


               // *** OPTION 2 ***
               .clickable(
                   interactionSource = interactionSource,
                   indication = null
               ) { clickToggleStep() }
        )
}


@Composable
fun SequencerGrid(){
   val padWidth = 40
   val interactions = remember { mutableStateListOf<Interaction>() }
   val anyStepPressedOrDragged = remember{ mutableStateOf(interactions.isNotEmpty()) }

   Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center
    ) {
        for (i in 1..16) {
            ToggleStep(
                padWidth = padWidth,
                interactionSource = interactionSource,
                anyStepPressedOrDragged = anyStepPressedOrDragged
            )
            Spacer(Modifier.size(2.dp))
        }
    }
}

With option 1, I am missing a way of passing the interactionSource to .pointerInput. I have seen .pointInput(interactionSource) in examples before, but if i understand correctly, this makes pointerInput observe the interactionSource for changes, as a key, and doesn't actually use the interaction source to handle its PointerEvents?

Option 2 was the original clickable modifier. I think this should successfully handle all PressInteractions using the same shared interactionSource, and so successfully affect the anyStepPressedOrDragged boolean. I just don't know how to extend/modify the .clickable modifier to add the drag detection.

Another option I had considered involved detecting a horizontal drag...

detectHorizontalDragGestures{change, _ ->
   val position = change.position

...and then elsewhere detecting whether 'position' was within the bounding box of another ToggleStep. But I'm not sure whether this is possible with an Offset object, as the Offset is calculated relative to the current element, rather than the window? I started trying to work out how to acheive this, but it seemed too convoluted...

...

I hope this explanation is clear enough. Thanks in advance!


Solution

  • Here is one best example of what you are looking for.

    In this example, you can find showing images in LazyVerticalGrid and also drag to select Gesture. so this will surely help you.

    example

    Please find it below.

    https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63