androidkotlinandroid-jetpack-composecompose-desktop

Jetpack Compose detect drag gesture and detect Interaction source


I want to develop a float draggable Button using jetpack compose, also I need to know when user drag interaction starts and when it ends.

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center,
        content = {
            val interactionSource = remember { MutableInteractionSource() }
            val interactions = remember { mutableStateListOf<Interaction>() }
            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collect { interaction ->
                    Log.i("dragInteraction", "-> $interaction")
                }
            }
            var offsetX by remember { mutableStateOf(0f) }
            var offsetY by remember { mutableStateOf(0f) }

            Surface(
                modifier = Modifier
                    .offset {
                        IntOffset(
                            x = offsetX.roundToInt(),
                            y = offsetY.roundToInt()
                        )
                    }
                    .size(60.dp)
                    .pointerInput(Unit) {
                        detectDragGestures { change, dragAmount ->
                            offsetX += dragAmount.x
                            offsetY += dragAmount.y
                            change.consume()
                        }
                    },
                interactionSource = interactionSource,
                onClick = {
                },
                content = {
                },
                color = Purple500
            )
        }
    )

in this code my Surface moves currectly but I can't get DragInteraction.Start and DragInteraction.Stop when I'm dragging it! all I get is androidx.compose.foundation.interaction.PressInteraction$Press@c38442d androidx.compose.foundation.interaction.PressInteraction$Cancel@e6d1ef3

any suggestion how can I detect drag state ?


Solution

  • detectDragGestures doesn't emit DragInteraction by default. You should either emit DragInteraction.Start, DragInteraction.Stop, DragInteraction.Cancel

    as

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun DragInteractionSample() {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center,
            content = {
    
                val interactionSource = remember { MutableInteractionSource() }
                val interactions = remember { mutableStateListOf<Interaction>() }
                var text by remember { mutableStateOf("") }
    
                LaunchedEffect(interactionSource) {
                    interactionSource.interactions.collect { interaction ->
                        when (interaction) {
                            is DragInteraction.Start -> {
                                text = "Drag Start"
                            }
                            is DragInteraction.Stop -> {
                                text = "Drag Stop"
                            }
                            is DragInteraction.Cancel -> {
                                text = "Drag Cancel"
                            }
                        }
                    }
                }
    
                val coroutineScope = rememberCoroutineScope()
    
                var offsetX by remember { mutableStateOf(0f) }
                var offsetY by remember { mutableStateOf(0f) }
    
                val modifier = Modifier
                    .offset {
                        IntOffset(
                            x = offsetX.roundToInt(),
                            y = offsetY.roundToInt()
                        )
                    }
                    .size(60.dp)
                    .pointerInput(Unit) {
    
                        var interaction: DragInteraction.Start? = null
                        detectDragGestures(
                            onDragStart = {
                                coroutineScope.launch {
                                    interaction = DragInteraction.Start()
                                    interaction?.run {
                                        interactionSource.emit(this)
                                    }
    
                                }
                            },
                            onDrag = { change: PointerInputChange, dragAmount: Offset ->
                                offsetX += dragAmount.x
                                offsetY += dragAmount.y
    
                            },
                            onDragCancel = {
                                coroutineScope.launch {
                                    interaction?.run {
                                        interactionSource.emit(DragInteraction.Cancel(this))
                                    }
                                }
                            },
                            onDragEnd = {
                                coroutineScope.launch {
                                    interaction?.run {
                                        interactionSource.emit(DragInteraction.Stop(this))
                                    }
                                }
                            }
                        )
                    }
    
                Surface(
                    modifier = modifier,
                    interactionSource = interactionSource,
                    onClick = {},
                    content = {},
                    color = MaterialTheme.colorScheme.primary
                )
    
                Text(text = text)
            }
        )
    }
    

    Result

    enter image description here

    or simply create an enum class and set it to a MutableState on each gesture function

    enum class DragState {
     Idle, DragStart, Drag, DragEnd, DragCancel
    }
    
    var dragState by remember{mutableStateOf(Idle}
    

    And change this state on every drag callback.