Building UI Using Jetpack Compose in Android
Jetpack Compose is a modern UI toolkit that is designed to simplify UI development in Android. It consists of a reactive programming model with conciseness and ease of Kotlin programming language. It is fully declarative so that you can describe your UI by calling some series of functions that will transform your data into a UI hierarchy. When the data changes or is updated then the framework automatically recalls these functions and updates the view for you.
Prerequisites:
- Familiar with Kotlin and OOP Concepts as well
- Basic understanding about Jetpack Compose
- Android Studio
Step by Step Implementation
Step 1: Create a New Android Studio Project
To create a new project in the Android Studio, please refer to How to Create a new Project in Android Studio with Jetpack Compose.
Step 2: Adding resources
There are some resources like colors, image assets, fonts, and some little things. You can easily find them otherwise just get them from the GitHub repo.
Step 3: Create Class for Bottom Section
BottomMenuContent.kt:
package com.geeksforgeeks.demo
import androidx.annotation.DrawableRes
data class BottomMenuContent (
val title: String,
@DrawableRes val iconId: Int
)
Step 4: Creating Class Course.kt
This section has five fields, so create five variables of their types. The fields you can see in the code as well.
Course.kt:
package com.geeksforgeeks.demo
import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
data class Course(
val title: String,
@DrawableRes val iconId: Int,
val lightColor: Color,
val mediumColor: Color,
val darkColor: Color
)
Step 5: Create a util file for design
PathUtil.kt
package com.geeksforgeeks.demo
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import kotlin.math.abs
// The function is standardQuadFromTo(m1,m2)
// taking two peramente those are nothing but points
// that we are created and just add.
fun Path.standardQuadFromTo(from: Offset, to: Offset) {
// this function is basically draw
// a line to our second point and
// also smooth on that line and make it curve
quadraticBezierTo(
from.x,
from.y,
abs(from.x + to.x) / 2f,
abs(from.y + to.y) / 2f
)
}
Step 6: Create a Kotlin class HomeScreen.kt
Instead of cluttering MainActivity.kt, we break the UI into reusable functions for better maintainability.
- Header Section – Displays two text elements and a search icon.
- Chips Section – Interactive filters for easy selection.
- Suggestion Section – A simple list of recommended content.
- Course Cards – The most complex part, featuring text, icons, and colors. We’ll create a data class and design smooth curves using Bezier paths.
- Bottom Section – Requires a data class to manage two views effectively.
- Course Section – Organizes course items in a grid layout.
Once structured, simply pass the course data:
CourseItem(course = courses[it])
HomeScreen.kt:
package com.geeksforgeeks.demo
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.*
import androidx.compose.foundation.shape.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import com.geeksforgeeks.demo.ui.theme.*
@ExperimentalFoundationApi
@Preview(widthDp = 700, heightDp = 1400)
@Composable
fun HomeScreen() {
// this is the most outer box having all the views inside it
Box(
modifier = Modifier
.background(DeepBlue)
.fillMaxSize()
) {
Column {
GreetingSection()
ChipSection(chips = listOf("Data structures", "Algorithms", "competitive programming", "python"))
SuggestionSection()
CourseSection(
courses = listOf(
Course(
title = "Geek of the Year",
R.drawable.ic_headphone,
BlueViolet1,
BlueViolet2,
BlueViolet3
),
Course(
title = "How does AI Works",
R.drawable.ic_videocam,
LightGreen1,
LightGreen2,
LightGreen3
),
Course(
title = "Advance python Course",
R.drawable.ic_play,
skyblue1,
skyblue2,
skyblue3
),
Course(
title = "Advance Java Course",
R.drawable.ic_headphone,
Beige1,
Beige2,
Beige3
),
Course(
title = "prepare for aptitude test",
R.drawable.ic_play,
OrangeYellow1,
OrangeYellow2,
OrangeYellow3
),
Course(
title = "How does AI Works",
R.drawable.ic_videocam,
LightGreen1,
LightGreen2,
LightGreen3
),
)
)
}
BottomMenu(items = listOf(
BottomMenuContent("Home", R.drawable.ic_home),
BottomMenuContent("Explore", R.drawable.ic_baseline_explore_24),
BottomMenuContent("Dark Mode", R.drawable.ic_moon),
BottomMenuContent("Videos", R.drawable.ic_videocam),
BottomMenuContent("Profile", R.drawable.ic_profile),
), modifier = Modifier.align(Alignment.BottomCenter))
}
}
@Composable
fun BottomMenu(
items: List<BottomMenuContent>,
modifier: Modifier = Modifier,
activeHighlightColor: Color = ButtonGreen,
activeTextColor: Color = Color.White,
inactiveTextColor: Color = AquaBlue,
initialSelectedItemIndex: Int = 0
) {
var selectedItemIndex by remember {
mutableIntStateOf(initialSelectedItemIndex)
}
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.background(DeepBlue)
.padding(15.dp)
) {
items.forEachIndexed { index, item ->
BottomMenuItem(
item = item,
isSelected = index == selectedItemIndex,
activeHighlightColor = activeHighlightColor,
activeTextColor = activeTextColor,
inactiveTextColor = inactiveTextColor
) {
selectedItemIndex = index
}
}
}
}
@Composable
fun BottomMenuItem(
item: BottomMenuContent,
isSelected: Boolean = false,
activeHighlightColor: Color = ButtonGreen,
activeTextColor: Color = Color.White,
inactiveTextColor: Color = AquaBlue,
onItemClick: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.clickable {
onItemClick()
}
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.background(if (isSelected) activeHighlightColor else Color.Transparent)
.padding(10.dp)
) {
Icon(
painter = painterResource(id = item.iconId),
contentDescription = item.title,
tint = if (isSelected) activeTextColor else inactiveTextColor,
modifier = Modifier.size(20.dp)
)
}
Text(
text = item.title,
color = if(isSelected) activeTextColor else inactiveTextColor
)
}
}
@Composable
fun GreetingSection(
name: String = "Geeks"
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(15.dp)
) {
Column(
verticalArrangement = Arrangement.Center
) {
Text(
text = "Good morning, $name",
style = MaterialTheme.typography.headlineLarge,
color = Color.White
)
Text(
text = "We wish you have a good day!",
style = MaterialTheme.typography.bodyLarge,
color = Color.Gray
)
}
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = "Search",
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
}
@Composable
fun ChipSection(
chips: List<String>
) {
var selectedChipIndex by remember {
mutableIntStateOf(0)
}
LazyRow {
items(chips.size) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(start = 15.dp, top = 15.dp, bottom = 15.dp)
.clickable {
selectedChipIndex = it
}
.clip(RoundedCornerShape(10.dp))
.background(
if (selectedChipIndex == it) ButtonGreen
else DarkerButtonGreen
)
.padding(15.dp)
) {
Text(text = chips[it], color = TextWhite)
}
}
}
}
@Composable
fun SuggestionSection(
color: Color = LightRed
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(15.dp)
.clip(RoundedCornerShape(10.dp))
.background(color)
.padding(horizontal = 15.dp, vertical = 20.dp)
.fillMaxWidth()
) {
Column {
Text(
text = "Daily Coding",
style = MaterialTheme.typography.headlineMedium,
color = Color.White
)
Text(
text = "Do at least • 3-10 problems / day",
style = MaterialTheme.typography.bodyLarge,
color = TextWhite
)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(ButtonGreen)
.padding(10.dp)
) {
Icon(
painter = painterResource(id = R.drawable.ic_play),
contentDescription = "Play",
tint = Color.White,
modifier = Modifier.size(16.dp)
)
}
}
}
@ExperimentalFoundationApi
@Composable
fun CourseSection(courses: List<Course>) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Courses",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(15.dp),
color = Color.White
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(start = 7.5.dp, end = 7.5.dp,bottom = 100.dp),
modifier = Modifier.fillMaxHeight()
) {
items(courses.size) {
CourseItem(course = courses[it])
}
}
}
}
@Composable
fun CourseItem(
course: Course
) {
BoxWithConstraints(
modifier = Modifier
.padding(7.5.dp)
.aspectRatio(1f)
.clip(RoundedCornerShape(10.dp))
.background(course.darkColor)
) {
val width = constraints.maxWidth
val height = constraints.maxHeight
// Medium colored path
val mediumColoredPoint1 = Offset(0f, height * 0.3f)
val mediumColoredPoint2 = Offset(width * 0.1f, height * 0.35f)
val mediumColoredPoint3 = Offset(width * 0.4f, height * 0.05f)
val mediumColoredPoint4 = Offset(width * 0.75f, height * 0.7f)
val mediumColoredPoint5 = Offset(width * 1.4f, -height.toFloat())
val mediumColoredPath = Path().apply {
moveTo(mediumColoredPoint1.x, mediumColoredPoint1.y)
standardQuadFromTo(mediumColoredPoint1, mediumColoredPoint2)
standardQuadFromTo(mediumColoredPoint2, mediumColoredPoint3)
standardQuadFromTo(mediumColoredPoint3, mediumColoredPoint4)
standardQuadFromTo(mediumColoredPoint4, mediumColoredPoint5)
lineTo(width.toFloat() + 100f, height.toFloat() + 100f)
lineTo(-100f, height.toFloat() + 100f)
close()
}
// Light colored path
val lightPoint1 = Offset(0f, height * 0.35f)
val lightPoint2 = Offset(width * 0.1f, height * 0.4f)
val lightPoint3 = Offset(width * 0.3f, height * 0.35f)
val lightPoint4 = Offset(width * 0.65f, height.toFloat())
val lightPoint5 = Offset(width * 1.4f, -height.toFloat() / 3f)
val lightColoredPath = Path().apply {
moveTo(lightPoint1.x, lightPoint1.y)
standardQuadFromTo(lightPoint1, lightPoint2)
standardQuadFromTo(lightPoint2, lightPoint3)
standardQuadFromTo(lightPoint3, lightPoint4)
standardQuadFromTo(lightPoint4, lightPoint5)
lineTo(width.toFloat() + 100f, height.toFloat() + 100f)
lineTo(-100f, height.toFloat() + 100f)
close()
}
Canvas(
modifier = Modifier
.fillMaxSize()
) {
drawPath(
path = mediumColoredPath,
color = course.mediumColor
)
drawPath(
path = lightColoredPath,
color = course.lightColor
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(15.dp)
) {
Text(
text = course.title,
style = MaterialTheme.typography.headlineMedium,
color = Color.White,
lineHeight = 26.sp,
modifier = Modifier.align(Alignment.TopStart)
)
Icon(
painter = painterResource(id = course.iconId),
contentDescription = course.title,
tint = Color.White,
modifier = Modifier.align(Alignment.BottomStart)
)
Text(
text = "Start",
color = TextWhite,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.clickable {
// Handle the click
}
.align(Alignment.BottomEnd)
.clip(RoundedCornerShape(10.dp))
.background(ButtonGreen)
.padding(vertical = 6.dp, horizontal = 15.dp)
)
}
}
}
Step 7: Working with MainActivity.kt
Now add fun HomeScreen() that contains all the functions, in MainActivity as
package com.geeksforgeeks.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import com.geeksforgeeks.demo.ui.theme.DemoTheme
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalFoundationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DemoTheme() {
HomeScreen()
}
}
}
}