Mehmet Peker
Mehmet Peker

Reputation: 527

Detect swipe direction on Jetpack Compose

I'm trying to detect swipe direction in Compose. I'm using the draggable modifier for this. But draggable allows only one direction to detect (Vertical or Horizontal). I want to detect swipes for all directions (left, right, up, down). Can anyone help me how can I do this? Thanks!

Upvotes: 14

Views: 15261

Answers (8)

Shamsul Arafin Mahtab
Shamsul Arafin Mahtab

Reputation: 559

I've created Modifier extension function for right and left swipe direction. Using this, you can also create up and down swipe using detectVerticalDragGestures.

fun Modifier.onSwipeRight(onSwipeRight: () -> Unit): Modifier {
    var dx = 0F

    return this.pointerInput(Unit) {
        detectHorizontalDragGestures(
            onDragEnd = {
                if (dx < 0) {
                    dx = 0F
                    onSwipeRight()
                }
            },
            onHorizontalDrag = { _, dragAmount ->
                dx = dragAmount
            })
    }
}


fun Modifier.onSwipeLeft(onSwipeRight: () -> Unit): Modifier {
    var dx = 0F

    return this.pointerInput(Unit) {
        detectHorizontalDragGestures(
            onDragEnd = {
                if (dx > 0) {
                    dx = 0F
                    onSwipeRight()
                }
            },
            onHorizontalDrag = { _, dragAmount ->
                dx = dragAmount
            })
    }
}

Upvotes: 0

padPad
padPad

Reputation: 199

You can use detectHorizontalDragGestures/detectVerticalDragGestures modifiers to detect Horizontal or Vertical drag gestures,

If you need to add more than one gesture listener to a composable, use separate pointerInput modifier instances

For Vertical Drag:

       Modifier.pointerInput(Unit) {
                detectVerticalDragGestures { _, dragAmount ->
                    if (dragAmount > 0) {
                        // swipe down
                    }else {
                        // swipe up
                    }

                }
            }

Upvotes: 4

Milan Jurkulak
Milan Jurkulak

Reputation: 625

My modification:

suspend fun PointerInputScope.detectSwipe(
        swipeState: MutableIntState = mutableIntStateOf(-1),
        onSwipeLeft: () -> Unit = {},
        onSwipeRight: () -> Unit = {},
        onSwipeUp: () -> Unit = {},
        onSwipeDown: () -> Unit = {},
    ) = detectDragGestures(
        onDrag = { change, dragAmount ->
            change.consume()
            val (x, y) = dragAmount
            if (abs(x) > abs(y)) {
                when {
                    x > 0 -> swipeState.intValue = 0
                    x < 0 -> swipeState.intValue = 1
                }
            } else {
                when {
                    y > 0 -> swipeState.intValue = 2
                    y < 0 -> swipeState.intValue = 3
                }
            }
        },
        onDragEnd = {
            when (swipeState.intValue) {
                0 -> onSwipeRight()
                1 -> onSwipeLeft()
                2 -> onSwipeDown()
                3 -> onSwipeUp()
            }
        }
    )

usage:

    Modifier.pointerInput(Unit) {
        detectSwipe(
           onSwipeLeft = {

           },
           onSwipeRight = {

           },
       )
   }

Upvotes: 2

dessalines
dessalines

Reputation: 7372

If you need to get the corner directions, here's an angle-based algorithm:

fun swipeDirection(x: Float, y: Float): SwipeDirection {
    val angleDir = (atan2(x.toDouble(), y.toDouble()) / Math.PI * 180)
    val angle = if (angleDir < 0) {
        360 + angleDir
    } else {
        angleDir
    }

    return when (angle) {
        in 22.5..67.5 -> {
            SwipeDirection.BOTTOM_RIGHT
        }
        in 67.5..112.5 -> {
            SwipeDirection.RIGHT
        }
        in 112.5..157.5 -> {
            SwipeDirection.TOP_RIGHT
        }
        in 157.5..202.5 -> {
            SwipeDirection.TOP
        }
        in 202.5..247.5 -> {
            SwipeDirection.TOP_LEFT
        }
        in 247.5..292.5 -> {
            SwipeDirection.LEFT
        }
        in 292.5..337.5 -> {
            SwipeDirection.BOTTOM_LEFT
        }
        else -> {
            SwipeDirection.BOTTOM
        }
    }
}

Upvotes: 2

Javier
Javier

Reputation: 1685

To complement the answers from @Gabriel and @J-Abdo I did something similar based on this answers, for me I need only left/right swipe and I added an "offset" with the min lenght that I want to be considered "swipe"

 BoxWithConstraints {
        var offsetX by remember { mutableStateOf(0f) }
        val minSwipeOffset by remember {
            mutableStateOf(
                constraints.maxWidth / 4
            )
        }
        Box(
            Modifier
                .fillMaxSize()
                .pointerInput(Unit) {
                    detectDragGestures(
                        onDrag = { change, dragAmount ->
                            change.consume()
                            val (x, y) = dragAmount
                            offsetX += dragAmount.x

                        },
                        onDragEnd = {

                            when {
                                (offsetX < 0F && abs(offsetX) > minSwipeOffset) -> {
                                    SwipeDirection.Left
                                }
                                (offsetX > 0 && abs(offsetX) > minSwipeOffset) -> {
                                    SwipeDirection.Right
                                }
                                else -> null

                            }?.let(onSwipe)
                            offsetX = 0F
                        }
                    )
                }) {
        }
    }

and I'm using just a sealed class for the directions


sealed class SwipeDirection {
    object Left : SwipeDirection()
    object Right : SwipeDirection()
}

Upvotes: 0

Gabriele Mariotti
Gabriele Mariotti

Reputation: 363765

You can use the pointerInput modifier controlling the dragging gesture with the detectDragGestures function.

Something like:

Box(modifier = Modifier.fillMaxSize()) {
    var offsetX by remember { mutableStateOf(0f) }
    var offsetY by remember { mutableStateOf(0f) }

    Box(
        Modifier
            .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
            .size(100.dp, 100.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consume()

                    val (x,y) = dragAmount
                    when {
                        x > 0 ->{ /* right */ }
                        x < 0 ->{ /* left */ }
                    }
                    when {
                        y > 0 -> { /* down */ }
                        y < 0 -> { /* up */ }
                    }

                    offsetX += dragAmount.x
                    offsetY += dragAmount.y
                }
            }
    )
}

Upvotes: 25

Bonnjalal
Bonnjalal

Reputation: 76

This is a more modified way to get the direction without conflict horizontal swipes with vertical swipes, and to make sure to return the direction after the user end swiping.

var direction by remember { mutableStateOf(-1)}

Box(
    modifier = Modifier
        .pointerInput(Unit) {
            detectDragGestures(
                onDrag = { change, dragAmount ->
                    change.consumeAllChanges()

                    val (x, y) = dragAmount
                    if(abs(x) > abs(y)){
                        when {
                            x > 0 -> {
                                //right
                                direction = 0
                            }
                            x < 0 -> {
                                // left
                                direction = 1
                            }
                        }
                    }else{
                        when {
                            y > 0 -> {
                                // down
                                direction = 2
                            }
                            y < 0 -> {
                                // up
                                direction = 3
                            }
                        }
                    }

                },
                onDragEnd = {
                    when (direction){
                        0 -> {
                            //right swipe code here }
                        1 -> {
                            // left swipe code here
                        }
                        2 -> {
                            // down swipe code here
                        }
                        3 -> {
                            // up swipe code here
                        }
                    }
                }
            )
        
)

Upvotes: 4

Noah
Noah

Reputation: 3425

Modifier.dragGestureFilter detects dragging in any direction. Pass an instance of DragObserver and override onDrag. Here you can detect the swipe direction based on the Offset. This object has x and y values, which are positive or negative based on the direction.

Here's what your code could look like:

Box(
  Modifier.dragGestureFilter(
    dragObserver = object : DragObserver() {
      override fun onDrag(dragDistance: Offset): Offset {
        val (x, y) = dragDistance
        when {
          x > 0 -> { /* right */ }
          x < 0 -> { /* left */ }
        }
        when {
          y > 0 -> { /* down */ }
          y < 0 -> { /* up */ }
        }
      }
    }
  )
)

To actually move the object, you would have to apply Modifier.offset with values that are updated in onDrag.

Upvotes: 7

Related Questions