Jetpack Compose: Dragging example

Introduction

Jetpack Compose, the UI toolkit for building native Android apps with minimal code, offers a unique, declarative approach to creating interactive UIs. With Jetpack Compose, developers can build responsive and flexible layouts efficiently, without relying on XML layouts. One of the powerful features Compose brings to the table is its ability to handle user interactions, including drag gestures, with just a few lines of code. In this example, we’ll dive into a simple yet effective drag-and-drop demonstration using Compose. This example is perfect for developers looking to get hands-on experience with gesture detection and UI updates in a Compose-based layout.

In this walkthrough, we’ll explore how this example leverages various Compose features like detectDragGestures to make a draggable component. This is achieved by defining and updating state variables to keep track of movement. With a clear, step-by-step breakdown of each function and component, you’ll understand how to apply these concepts to create interactive, gesture-enabled UIs in your Android apps.

Description

The example begins in the MainActivity class, where we override the onCreate method to set the content of the activity using the setContent function. This function sets up our Compose UI by invoking the MainContent composable function, which contains all the elements we’ll see on the screen. The MainContent composable uses a Column layout to arrange the content vertically, with each element spaced by 25 dp for a clean and organized look.

Within the MainContent function, two essential variables, offsetX and offsetY, are defined to track the horizontal and vertical positions of a draggable box. These state variables, defined using the remember function, allow Compose to monitor changes in position and recompose the UI as needed. The Column modifier properties help set up a background color, padding, and fill the screen space, ensuring a visually consistent layout across devices.

The first Box within the Column is our draggable component. This box is styled with rounded corners and a red background to make it stand out as an interactive element. Its position is dynamically controlled by the offset modifier, which takes IntOffset values for both x and y coordinates. These coordinates are derived by rounding the offsetX and offsetY float values to integers. This box also includes the pointerInput modifier, which enables gesture detection by listening for drag actions through the detectDragGestures method. When a drag is detected, the consumeAllChanges function ensures that the gesture is fully handled by the box, preventing interference with other components. The dragAmount values, which represent changes in x and y during the drag, are then added to the offset variables, smoothly updating the position of the box as it is dragged around.

The draggable box displays the text "Drag Me" in large, centered text, making its purpose clear. This text uses the Text composable with customizable font size, color, and alignment. Positioned in the center of the box, it invites user interaction and offers a straightforward example of handling gestures in Compose.

Below the draggable box, a second Box component serves as a static element labeled "Non Movable." This box has similar styling, with rounded corners and padding, but lacks the gesture detection setup, so it remains in a fixed position. This stationary box demonstrates how to incorporate both interactive and non-interactive elements within the same layout, allowing developers to see how gesture handling can be applied selectively.

Summary

This example provides a straightforward introduction to handling drag gestures in Jetpack Compose. By defining stateful offset variables and using pointerInput with detectDragGestures, it demonstrates how easy it is to make components draggable. This interactivity, achieved with minimal code, highlights the flexibility of Jetpack Compose in handling dynamic, gesture-based layouts. Additionally, the separation between draggable and static elements showcases how you can mix different types of UI components seamlessly.

With this example, developers can start implementing draggable features in their applications, enhancing user engagement and interactivity. It also serves as a foundation for further exploration into complex gestures, like swiping or scaling, within Compose layouts. Whether you're building custom UIs or adding subtle, engaging animations, the principles shown here will help you create responsive and intuitive user experiences.


MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.roundToInt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MainContent()
        }
    }


    @Composable
    fun MainContent(){
        Column(
            modifier = Modifier
                .background(Color(0xFFEDEAE0))
                .padding(16.dp)
                .fillMaxSize(),
            verticalArrangement = Arrangement.spacedBy(25.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            var offsetX by remember { mutableStateOf(0f) }
            var offsetY by remember { mutableStateOf(0f) }

            Box(
                Modifier
                    .offset {
                        IntOffset(
                            x = offsetX.roundToInt(),
                            y = offsetY.roundToInt()
                        )
                    }
                    .clip(RoundedCornerShape(16.dp))
                    .background(Color(0xFFE63E62))
                    .padding(25.dp)
                    .pointerInput(Unit) {
                        detectDragGestures { change, dragAmount ->
                            change.consumeAllChanges()
                            offsetX += dragAmount.x
                            offsetY += dragAmount.y
                        }
                    }
            ){
                Text(
                    text = "Drag Me",
                    fontSize = 30.sp,
                    color = Color(0xFFFFDDF4),
                    textAlign = TextAlign.Center,
                )
            }

            Box(
                modifier = Modifier
                    .clip(RoundedCornerShape(16.dp))
                    .background(Color(0xFF58427C))
                    .padding(25.dp)
            ){
                Text(
                    text = "Non Movable",
                    fontSize = 30.sp,
                    color = Color(0xFFFFFFFF),
                    textAlign = TextAlign.Center,
                )
            }
        }
    }


    @Preview
    @Composable
    fun ComposablePreview(){
        //MainContent()
    }
}
More android jetpack compose tutorials