Jetpack Compose: How to make a Card clickable

Introduction

In Android app development, creating interactive UI elements is essential for enhancing user experience. Jetpack Compose, Android’s modern toolkit for building native UI, offers developers a declarative way to design interfaces with reactive components. One common task in UI design is making components clickable, such as displaying a counter that updates on tap or click. In Jetpack Compose, implementing click behavior is streamlined and straightforward, allowing you to control how elements respond to user interactions with minimal boilerplate.

This article explains how to create clickable cards in Jetpack Compose. In the example code, we build two cards that react differently when clicked: one card increments a counter when tapped, and the other decrements it. By following this breakdown, you’ll gain a solid understanding of how to use Jetpack Compose to handle user interactions and dynamically update the UI.

Code Breakdown: Main Activity Setup

The entry point of the app is the MainActivity class, which extends AppCompatActivity. Within the onCreate method, the setContent function is used to define the app’s content. Here, setContent is called with the GetScaffold composable function, which initializes the app's main screen layout. This setup reflects the flexibility of Jetpack Compose, as it allows the UI to be defined directly within Kotlin functions rather than XML.

The GetScaffold function wraps the app’s UI within a Scaffold composable, which is designed to handle basic UI structure components like the top bar, bottom bar, floating action buttons, and more. In this case, the Scaffold includes a TopAppBar that displays the title “Compose - Card Clickable” with a background color. Additionally, the main content of the screen is set to the MainContent composable, defining the core interactive elements.

Main Content Structure

The MainContent function is where the primary UI elements are organized. Inside, a Column composable is used to structure the layout vertically. This Column is set to take up the entire screen size with fillMaxSize, and it includes padding around the edges for visual spacing. By specifying Arrangement.SpaceEvenly, the child elements within the Column are spaced evenly, providing a balanced layout. horizontalAlignment is set to Alignment.CenterHorizontally, ensuring the elements are aligned in the center of the screen horizontally.

A mutable state variable counter is created using remember and mutableStateOf. This variable tracks the count displayed in the cards, and its value is updated in response to user interactions. Using remember allows the state to persist across recompositions, so the counter value is retained as users interact with the app.

First Card: Incrementing the Counter

The first Card composable is styled with a background color, elevation, and rounded corners. It spans the full width of the screen, with a fixed height of 150 dp. The clickable modifier is applied to this card, which defines the interaction behavior when the card is tapped. Inside the clickable block, the counter variable is incremented by one each time the card is clicked.

Inside the card, a Box composable is used to center a Text composable, which displays the current value of counter. This is an example of Jetpack Compose’s flexibility, as UI components and data are tightly integrated. Whenever the counter value changes, Jetpack Compose automatically re-composes the UI, updating the displayed count in real-time without any manual intervention.

Second Card: Decrementing the Counter

The second Card is similar in layout and style to the first but demonstrates an alternative method for handling clicks. Instead of using the clickable modifier, this card uses the pointerInput modifier combined with the detectTapGestures function to manage touch gestures. This approach offers more customization for tap events, as it allows for separate gesture detection, including long-press, double-tap, and more. In this case, onTap is defined to decrement the counter by one each time the card is tapped.

This card has a red background color to visually distinguish it from the first. By using pointerInput with detectTapGestures, the code showcases how to go beyond basic clicks and implement more advanced gesture recognition in Jetpack Compose. This flexibility is particularly useful in scenarios where you want to provide custom interactions beyond simple clicks.

Preview Function

The ComposablePreview function serves as a preview setup, allowing developers to view the UI within Android Studio’s preview pane. Although commented out in this code, invoking GetScaffold() inside ComposablePreview would enable a visual preview of the app’s main screen without needing to run the app on an emulator or physical device. This feature is extremely useful for speeding up the UI design process.

Summary

This example demonstrates how to create interactive cards in Jetpack Compose by utilizing modifiers like clickable and pointerInput to handle user interactions. Jetpack Compose’s declarative UI approach simplifies state management and reactivity, making it easier for developers to design responsive interfaces. With just a few lines of code, it’s possible to create complex behavior, such as updating a counter in real-time based on user clicks.

Whether you’re building a simple counter or a more complex app, understanding how to handle click events and manage state in Jetpack Compose is essential. This example serves as a foundational pattern for building interactive elements, illustrating the flexibility and power of Jetpack Compose in creating modern Android applications.


MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

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

        setContent {
            GetScaffold()
        }
    }


    @Composable
    fun GetScaffold(){
        Scaffold(
            topBar = {TopAppBar(
                title = {Text(
                    "Compose - Card Clickable",
                    color = Color.White)},
                backgroundColor = Color(0xFF58427C)) },
            content = {MainContent()},
            backgroundColor = Color(0xFFEDEAE0)
        )
    }


    @Composable
    fun MainContent(){
        var counter by remember { mutableStateOf(0)}

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(12.dp),
            verticalArrangement = Arrangement.SpaceEvenly,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(150.dp)
                    .clickable{
                        counter ++
                    },
                backgroundColor = Color(0xFF319177),
                elevation = 4.dp,
                shape = RoundedCornerShape(24.dp),
            ) {
                Box(Modifier.wrapContentSize(Alignment.Center)) {
                    Text(
                        text = "$counter",
                        style = MaterialTheme.typography.h1
                    )
                }
            }

            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(150.dp)
                    .pointerInput(UInt){
                        detectTapGestures(
                            onTap = { counter-- }
                        )
                    },
                backgroundColor = Color(0xFFE95C4B),
                elevation = 4.dp,
                shape = RoundedCornerShape(24.dp),
            ) {
                Box(Modifier.wrapContentSize(Alignment.Center)) {
                    Text(
                        text = "$counter",
                        style = MaterialTheme.typography.h1
                    )
                }
            }
        }
    }


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