Android Compose Tutorial - Label Selector Bar

Ken Ruiz Inoue
Deuk
Published in
8 min readJan 6, 2024

Introduction

Hello! Welcome to my latest Android Compose tutorial! Today, we are crafting a user interface that provides a selection of labels. This versatile UI component allows users to choose a single option from a group, perfect for selecting categories, types, or any classification you need.

Looking to dive straight into the code? The repository is ready here.

On Today’s Agenda

  1. Implementing LabelUi(): We will start by building LabelUi(), a composable representing an individual label within our selection group.
  2. Crafting LabelSelectorBar(): Following that, we will assemble LabelSelectorBar(), which serves as a container for our collection of labels, enabling a clear and concise selection process.

Environment

  • Android Studio Hedgehog | 2023.1.1
  • Compose version: androidx.compose:compose-bom:2023.08.00
  • Pixel 5 API 32 Emulator

Step 1: LabelUI Implementation

Let’s dive in by setting up the composable/LabelUi.kt file with the following code:

// Your package...

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun LabelUi(
text: String = "",
selected: Boolean = false,
// 1. Layout Customization Parameters
labelTextStyle: TextStyle = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
),
backgroundColor: Color = Color.White,
selectedBackgroundColor: Color = Color.Black,
textColor: Color = Color.Black,
selectedTextColor: Color = Color.White,
roundedCornerShapeSize: Dp = 8.dp,
horizontalPadding: Dp = 16.dp,
verticalPadding: Dp = 8.dp,
onClick: () -> Unit = {},
) {
// 2. Interaction Feedback Removal
val interactionSource = remember { MutableInteractionSource() }
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
)
.background(
// 3. Label Color Depending the selected Value
if (selected) selectedBackgroundColor else backgroundColor,
RoundedCornerShape(roundedCornerShapeSize)
)
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
) {
Text(
text = text,
// 4. Text Color Depending the selected Value
color = if (selected) selectedTextColor else textColor,
style = labelTextStyle
)
}
}

@Preview
@Composable
fun LabelUiPreview() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
LabelUi(text = "Label1", selected = true)
LabelUi(text = "Label2", selected = false, textColor = Color.Blue)
}
}
  1. Layout Customization Parameters: These parameters include labelTextStyle, which determines the style of the label's text, such as its font weight and size; backgroundColor and selectedBackgroundColor, which define the label's background color in its default and selected states, respectively; textColor and selectedTextColor, which set the color of the text content for both states; and roundedCornerShapeSize, horizontalPadding, and verticalPadding, which control the shape and padding of the label. By providing these properties as parameters with default values, we can make LabelUi() highly reusable and adaptable to different UI contexts.
  2. Interaction Feedback Removal: The interactionSource is a Compose utility that tracks the interaction state of the clickable component. By setting indication = null in the clickable modifier, you are effectively removing the visual feedback that typically occurs when the user interacts with the clickable area (such as a ripple effect on Android). This could create a cleaner UI design when you don’t want to show any state changes when the user clicks the label.
  3. Dynamic Background Color: The background color of the LabelUi() is determined by whether the selected parameter is true or false. If selected is true, the selectedBackgroundColor (Black by default) is used; otherwise, the backgroundColor (White by default) is applied. This allows the label to reflect its selection state to the user visually.
  4. Dynamic Text Color: Similar to the previous point, the color of the text within the LabelUi is determined by the selected state. If the label is selected, the selectedTextColor (White by default) is used; if it is not selected, the textColor (Black by default) is used. This change in text color helps to enhance the visibility of the selected label against the background color change.

When the preview is executed, you should observe two vertically stacked labels showcasing their selected and default states.

Step 2: Label Selector Bar Implementation

Next, create the composable/LabelSelectorBar.kt file:

// Your package...

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun LabelSelectorBar(
labelItems: List<String> = listOf(),
// 1. LabelSelectorBar Customization Parameters
barHeight: Dp = 56.dp,
horizontalPadding: Dp = 8.dp,
distanceBetweenItems: Dp = 0.dp,
// 2. LabelUi Customization Parameters
labelTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp),
backgroundColor: Color = Color.White,
selectedBackgroundColor: Color = Color.Black,
textColor: Color = Color.Black,
selectedTextColor: Color = Color.White,
roundedCornerShapeSize: Dp = 8.dp,
labelHorizontalPadding: Dp = 16.dp,
labelVerticalPadding: Dp = 8.dp,
) {
// 3. Stateful Selection Management:
val selectedLabel = rememberSaveable { mutableStateOf(labelItems.firstOrNull() ?: "") }
LazyRow(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.height(barHeight)
) {
item { Spacer(modifier = Modifier.width(horizontalPadding)) }
// 4. Interactive Label Identification
items(labelItems) { label ->
LabelUi(
text = label,
selected = label == selectedLabel.value,
labelTextStyle = labelTextStyle,
backgroundColor = backgroundColor,
selectedBackgroundColor = selectedBackgroundColor,
textColor = textColor,
selectedTextColor = selectedTextColor,
roundedCornerShapeSize = roundedCornerShapeSize,
horizontalPadding = labelHorizontalPadding,
verticalPadding = labelVerticalPadding
) {
selectedLabel.value = label
}
Spacer(modifier = Modifier.width(distanceBetweenItems))
}
item { Spacer(modifier = Modifier.width(horizontalPadding)) }
}
}

@Preview
@Composable
fun LabelSelectorBarPreview() {
LabelSelectorBar(labelItems = listOf("All", "Pop", "Rock", "Jazz", "Hip Hop", "Classical"))
}
  1. LabelSelectorBar() Customization Parameters: The barHeight parameter sets the height of the bar, horizontalPadding defines the padding at the start and end of the LazyRow(), and distanceBetweenItems specifies the space between each label item in the row. These parameters allow for flexible layout design, making the LabelSelectorBar adaptable to different screen sizes and design requirements.
  2. LabelUi() Customization Parameters: These parameters are used to benefit the LabelUi() function from a range of parameters that offer extensive customization options.
  3. Stateful Selection Management: A selectedLabel state is created and remembered across recompositions, which holds the currently selected label. If no label is initially selected, it defaults to an empty string. This state is crucial for identifying active labels and handling user interactions within the label bar.
  4. Interactive Label Identification: Labels in the LabelSelectorBar() are identified by their text, streamlining the selection process. Clicking a label updates the selectedLabel state, reflecting the choice in the UI. This approach works well for simple cases. However, for complex setups, unique IDs for each label are recommended for better scalability and to avoid issues with duplicate or dynamic label texts.

By running the preview function, you should have a container with some items as Labels and only 1 item selected at a time.

Step 3: LabelSelectorBar Demonstration

To demonstrate the LabelSelectorBar() in action, let’s integrate it into the MainActivity.kt file. The code should be updated as follows:

// Your package...

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.kenruizinoue.labelselectorbarcomposable.composable.LabelSelectorBar

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LabelSelectorBar(
labelItems = listOf(
"All", "Pop", "Rock", "Jazz", "Hip Hop", "Classical"
),
barHeight = 80.dp,
horizontalPadding = 12.dp,
distanceBetweenItems = 8.dp,
backgroundColor = Color(0xFFB2F0AD),
selectedBackgroundColor = Color(0xFF294D16),
textColor = Color(0xFF333333),
selectedTextColor = Color(0xFFD8D1D1),
roundedCornerShapeSize = 24.dp,
labelVerticalPadding = 14.dp,
labelHorizontalPadding = 18.dp,
)
}
}
}

Run the app to showcase its customizability!

Next Steps

As we wrap up this tutorial on the LabelSelectorBar(), it’s important to recognize its role within the larger context of the MusicStreamingDemoApp. This composable is a key piece in an app that, when assembled, forms an elegant and functional music streaming application.

The beauty of this project lies in its modular design. You can create the four main components in any order you prefer, giving you the flexibility to approach the development process in a way that suits your style or needs best.

The Four Key UI Components

TopBar: This is where users select music from a list. (this tutorial)

TrackList: A dynamic display showcasing various tracks. It’s an interactive segment where users can browse and choose their next listen. (coming soon!)

PlaybackBar: The control center of the music experience. It allows users to play, pause, and navigate through the music.

BottomBar: Navigation hub of the app. (coming soon!)

After creating the individual components, the last step involves integrating them into the main screen of the music app. This is where you see the harmony of the TopBar, TrackList, PlaybackBar, and BottomBar in action, culminating in a user-friendly and visually appealing music streaming interface. (coming soon!)

For those eager to dive into the most current iteration of the project, the MusicStreamingDemoApp repository awaits your exploration here.

Discover More

Are these tutorials sparking your curiosity? Let’s dive even deeper into the fascinating world of Android development!

Wrapping Up

I hope you found this tutorial both informative and inspiring. Feel free to take your time to digest the code, experiment with it, and make it your own! Remember, understanding comes with practice and exploration.

If any questions or doubts arise, don’t hesitate to reach out. I’m here to assist you in your journey to becoming a more proficient Android developer.

If you enjoyed this content and are looking forward to more insightful and educational experiences, please consider supporting my work. A clap, a follow, or sharing this tutorial can make a big difference!

See you in the next tutorial, where we will continue to unravel the exciting world of Android Compose!

Deuk Services: Your Gateway to Leading Android Innovation

Are you looking to boost your business with top-tier Android solutions?Partner with Deuk services and take your projects to unparalleled heights.

--

--