XCarb
XCarb

Reputation: 937

Kotlin Jetpack Compose DragGesture property cancel the scrolling of my View

I'am new to jetpack compose and i really liked it. But ran into a problem : I want know if my view is swiped up or down so i created a LazyColumn with some item in it to be able to scroll something. It work fine but i would like to access the Gesture property to know if the view is scrolled down or up, here is my code :

LazyColumn{
            items (100){
                Text(
                    text = "Item $it",
                    fontSize = 24.sp,
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(vertical = 24.dp)
                        .pointerInput(Unit) {
                        detectDragGestures { change, dragAmount ->
                            //change.consumeAllChanges()// i don't know if this does something, i tried to remove it
                            println("detectDragGestures")
                            val (x, y) = dragAmount
                            if(abs(x) < abs(y)){
                                if (y > 0)
                                    println("drag down")
                                else
                                    println("drag Up")
                            }
                        }
                    })
                }
            }

This work, i can detect if the view is scrolled down or up, the problem is when i tap on the item and scroll, i get the right print but the view isn't scrolled, i have to click between item to be able to scroll.

I don't really know how gesture work in jetpack compose but i would like to get the direction of the swipe without preventing my view to be scrolled.

Upvotes: 3

Views: 4683

Answers (2)

Thracian
Thracian

Reputation: 66869

Explanation of how gesture system in Jetpack Compose works in detail here and here

change.consumeAllChanges() is deprecated now, partial consumes are deprecated. change.consume() is the only consume function to be used. What consume does is it returns change.isConsumed true and because of that any drag, scroll, transform gesture stops progressing or receiving events. detectDragGestures and detectTransformGestures consume events by default so next

Modifier.pointerInput() doesn't get these events. What you commented doesn't mean anything since drag already consumes events.

Here it's source code.

suspend fun PointerInputScope.detectDragGestures(
    onDragStart: (Offset) -> Unit = { },
    onDragEnd: () -> Unit = { },
    onDragCancel: () -> Unit = { },
    onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
) {
    forEachGesture {
        awaitPointerEventScope {
            val down = awaitFirstDown(requireUnconsumed = false)
            var drag: PointerInputChange?
            var overSlop = Offset.Zero
            do {
                drag = awaitPointerSlopOrCancellation(
                    down.id,
                    down.type
                ) { change, over ->
                    change.consume()
                    overSlop = over
                }
            // ! EVERY Default movable GESTURE HAS THIS CHECK
            } while (drag != null && !drag.isConsumed)
            if (drag != null) {
                onDragStart.invoke(drag.position)
                onDrag(drag, overSlop)
                if (
                    !drag(drag.id) {
                        onDrag(it, it.positionChange())
                        it.consume()
                    }
                ) {
                    onDragCancel()
                } else {
                    onDragEnd()
                }
            }
        }
    }
}

What you can do is using nestedScroll on parent of LazyColumn as here

@Composable
private fun NestedScrollExample() {

    var text by remember { mutableStateOf("") }


    val nestedScrollConnection = remember {
        object : NestedScrollConnection {

            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                text = "onPreScroll()\n" +
                        "available: $available\n" +
                        "source: $source\n\n"
                return super.onPreScroll(available, source)
            }

            override fun onPostScroll(
                consumed: Offset,
                available: Offset,
                source: NestedScrollSource
            ): Offset {
                text += "onPostScroll()\n" +
                        "consumed: $consumed\n" +
                        "available: $available\n" +
                        "source: $source\n\n"
                return super.onPostScroll(consumed, available, source)
            }

            override suspend fun onPreFling(available: Velocity): Velocity {
                text += "onPreFling()\n" +
                        " available: $available\n\n"
                return super.onPreFling(available)
            }

            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
                text += "onPostFling()\n" +
                        "consumed: $consumed\n" +
                        "available: $available\n\n"
                return super.onPostFling(consumed, available)
            }
        }
    }

    Column() {
        Box(
            Modifier
                .weight(1f)
                .nestedScroll(nestedScrollConnection)
        ) {
            LazyColumn(
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                items(100) {
                    Text(
                        text = "I'm item $it",
                        modifier = Modifier
                            .shadow(1.dp, RoundedCornerShape(5.dp))
                            .fillMaxWidth()
                            .background(Color.LightGray)
                            .padding(12.dp),
                        fontSize = 16.sp,
                        color = Color.White
                    )
                }
            }
        }

        Spacer(modifier = Modifier.height(10.dp))
        Text(
            text = text,
            modifier = Modifier
                .fillMaxWidth()
                .verticalScroll(rememberScrollState())
                .height(250.dp)
                .padding(10.dp)
                .background(BlueGrey400),
            fontSize = 16.sp,
            color = Color.White
        )
    }
}

enter image description here

It will return if you scroll up or down on your LazyColumn, if you want to do this only when your items are being scrolled you can do it as

Column(modifier = Modifier.fillMaxSize()) {

    var text by remember { mutableStateOf("Drag to see effects") }
    Text(text)

    LazyColumn {
        items(100) {
            Text(
                text = "Item $it",
                fontSize = 24.sp,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .border(3.dp, Color.Green)
                    .fillMaxSize()
                    .padding(vertical = 24.dp)
                    .pointerMotionEvents(Unit,
                        onDown = {
                                 it.consume()
                        },

                        onMove = { change ->
                            val position = change.positionChange()

                            val dragText = if (position.y < 0) {
                                "drag up"
                            } else if (position.y > 0) {
                                "drag down"
                            } else {
                                "idle"
                            }

                            text = "position: ${change.position}\n" +
                                    "positionChange: ${change.positionChange()}\n" +
                                    "dragText: $dragText"
                        }
                    )
            )
        }
    }
}

enter image description here

Modifier.pointerMotionEvents() is a gesture Modifier i wrote, it's available here.

Upvotes: 2

talhatek
talhatek

Reputation: 355

I managed to detect scroll direction with using Column instead of LazyColumn.

I hope it can lead you.

@Composable
fun ScrollDetect() {
    val scrollState = rememberScrollState()
    var dragPosition by remember {
        mutableStateOf(0)
    }
    LaunchedEffect(key1 = scrollState.value, block = {
        if (scrollState.value > dragPosition)
            Log.e("dragging", "up")
        else
            Log.e("dragging", "down")
        dragPosition = scrollState.value

    })
    Column(
        modifier = Modifier
            .verticalScroll(scrollState)
    )
    {
        repeat(100) {
            Text(
                text = "Item $it",
                fontSize = 24.sp,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .padding(vertical = 24.dp)

            )
        }
    }
}

Upvotes: 0

Related Questions