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
So, we are just going to create an application UI using jetpack compose (Kotlin). our final UI will look like it.
It’s’ an easy UI to build using jetpack compose but a little bit hard using XML. So, let’s start step by step.
Step by Step Implementation
Step 1: Create a new android studio project
To create a new project in Android Studio using Jetpack Compose please refer to:- How to Create a New Project in Android Studio Canary Version with Jetpack Compose.
Step 2: Let’s Add resources to the project
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 a Kotlin class HomeScreen.kt
We can do the same task in MainActivity.kt as well, but it’s good practice to create another file. Initially, we start from the top, so creating the first header of the UI. having two texts with an image of a search icon. Things are very easy in this case we just create functions and call them, and that is how we can reuse the code very easily.
package com.cuid.geekscourse.ui.theme
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.cuid.composeui.R
// here we have created HomeScreen function // and we will call all functions inside it. // and finally just call this function from mainActivity @Composable fun HomeScreen() { // this is the most outer box that will
// contain all the views,buttons,chips,etc.
Box(
modifier = Modifier
.background(DeepBlue)
.fillMaxSize()
) {
Column {
// this is how we call
// function adding whole UI
GreetingSection()
}
}
@Composable // and this is the function // just for creating header at top fun GreetingSection( name: String = "Geeks"
) { // here we just arrange the views
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding( 15 .dp)
) {
Column(
verticalArrangement = Arrangement.Center
) {
// heading text view
Text(
text = "Good morning, $name" ,
style = MaterialTheme.typography.h1
)
Text(
text = "We wish you have a good day!" ,
style = MaterialTheme.typography.body1
)
}
// search icon
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = "Search" ,
tint = Color.White,
modifier = Modifier.size( 24 .dp)
)
}
} |
Step 4: Create another function for chips
As we have shown above in-app preview that we can change chips. So let’s build it.
// This is how we can create chip seaction at the top of app @Composable fun ChipSection( // function with single argument
chips: List<String>
) { var selectedChipIndex by remember {
// it will not update the string
// but save and it will helpful for us
mutableStateOf( 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(
// this is basic condition for selected chip index
if (selectedChipIndex == it) ButtonGreen
else DarkerButtonGreen
)
.padding( 15 .dp)
) {
Text(text = chips[it], color = TextWhite)
}
}
}
} |
Step 5: Create a function for SuggestionSecation
It’s really a very basic part of this app.
// This function is for suggestion secation @Composable fun SuggestionSection( color: Color = LightBlue
) { 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 {
// here are two text views or we can say only text
Text(
text = "Daily Coding" ,
// it can be litile bit confusing but
// it is just text style alternate
// of fontfamily in XML
style = MaterialTheme.typography.h2
)
Text(
// same as above
text = "do at least • 3-10 problems / day" ,
style = MaterialTheme.typography.body1,
color = TextWhite
)
}
Box(
// box containing icon
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)
)
}
}
} |
The next step should be the card of courses but that is not easy or we can say the toughest part of the whole UI, so we will do it after the last one.
- Let’s Create a BottomSection, but before that, we should create the class for simplicity
- because our BottomSection element has two fields or views so let’s do
Step 6: Create Class for ButtomSection (BottomMenuContent.kt)
package com.cuid.geekscourses
import androidx.annotation.DrawableRes
// having two parameters title and iconid data class BottomMenuContent(
val title: String,
@DrawableRes val iconId: Int
) |
Step 7: Create function for ButtomSection
@Composable // this function tells us that // how menu item should look like 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 {
mutableStateOf(initialSelectedItemIndex)
}
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.background(DeepBlue)
.padding( 15 .dp)
) {
// it is basically what we should have
// for creating an element of BottomMenuItem
items.forEachIndexed { index, item ->
BottomMenuItem(
item = item,
isSelected = index == selectedItemIndex,
activeHighlightColor = activeHighlightColor,
activeTextColor = activeTextColor,
inactiveTextColor = inactiveTextColor
) {
selectedItemIndex = index
}
}
}
} // it's basically how menu item should look like @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()
}
) {
// here are some peremetens
// for how elements will align
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,
// it's basic condition
color = if (isSelected) activeTextColor else inactiveTextColor
)
}
} |
Now, the final part of the app is a little bit tricky, which is nothing but the course item, this one:
Here we have three colors, string and icon as well, then same as above we will create a class, and set data during the function call
Step 8: 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.
package com.cuid.geekscourses
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
) |
Now just think about it, that how to make those curves on the card. So basically, we set some points and create a smooth curve by joining points created
Step 9: Creating CourseSection
It’s basically just how our grid will Arrange.
@ExperimentalFoundationApi @Composable // here we have just passed the list of courses fun CourseSection(courses: List<Course>) { Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = "courses" ,
style = MaterialTheme.typography.h1,
modifier = Modifier.padding( 15 .dp)
)
// we have used lazyVertically grid
LazyVerticalGrid(
cells = GridCells.Fixed( 2 ), // it basically tells no. of cells in a row
contentPadding = PaddingValues(start = 7.5 .dp, end = 7.5 .dp,bottom = 100 .dp),
modifier = Modifier.fillMaxHeight()
) {
items(courses.size) {
// here we have to define how one of these item is look like
// we will tell after defining item design
// let me comment it for now and after
// creating you just have to remove
// CourseItem(course = courses[it])
}
}
}
} |
Step 10: Creating Course Card Item Design
@Composable fun CourseItem( course: Course
) { BoxWithConstraints(
// Box with some attributes
modifier = Modifier
.padding( 7.5 .dp)
.aspectRatio(1f)
.clip(RoundedCornerShape( 10 .dp))
.background(feature.darkColor)
) {
val width = constraints.maxWidth
val height = constraints.maxHeight
// setting 5 points for medium
// color or we can say for another
// 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())
// joining points to make curves with the help of path class
// path file that we have created earlier
// having function that just help to reduce our code
// and the function is standardQuadFromTo(m1,m2) taking
// two peramente and connect them
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()
}
// it's another part of that
// texture with light color
// 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 is used when we
// want to draw something
Canvas(
modifier = Modifier
.fillMaxSize()
) {
drawPath(
// function for drawing paths
// just pass the path
path = mediumColoredPath,
color = course.mediumColor
)
drawPath( // it's for the lighter path
path = lightColoredPath,
color = course.lightColor
)
}
// so , we have done with texture and
// now just creating box and other things
// box containing course elements
Box(
modifier = Modifier
.fillMaxSize()
.padding( 15 .dp)
) {
Text(
text = course.title,
style = MaterialTheme.typography.h2,
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 clicks
}
.align(Alignment.BottomEnd)
.clip(RoundedCornerShape( 10 .dp))
.background(ButtonGreen)
.padding(vertical = 6 .dp, horizontal = 15 .dp)
)
}
}
} |
That’s all about, how our item will look like. So just pass the item in the course section
CourseItem(course = courses[it])
It’s that file that we have discussed above to reduce our code
package com.cuid.geekscourses
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
)
} |
Now we have all the things ready so just call all the functions and see your result as
@ExperimentalFoundationApi @Composable fun HomeScreen() { // this is the most outer box
// having all the views inside it
Box(
modifier = Modifier
.background(DeepBlue)
.fillMaxSize()
) {
Column {
// this is the function for header
GreetingSection()
// it's for chipsSecation, and pass
// as many strings as you want
ChipSection(chips = listOf( "Data structures" , "Algorithms" , "competitive programming" , "python" ))
// function for suggestionSection
suggestionSection()
// this is for course secation
CourseSection(
// function require list of courses and
// one course contain 5 attributes
courses = listOf(
Course(
title = "geek of the year" ,
R.drawable.ic_headphone,
// these are colors.......
BlueViolet1,
BlueViolet2,
BlueViolet3
),
// below are the copies of the objects
// and you can add as many as you want
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
),
)
)
}
// this is the final one that is bottomMenu
BottomMenu(items = listOf(
// having 5 instances
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))
}
} |
Now add fun HomeScreen() that contains all the functions, in MainActivity as
package com.cuid.geekscourses
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import com.cuid.geekscourses.ui.HomeScreen
import com.cuid.geekscourses.ui.theme.Geekscourse
class MainActivity : ComponentActivity() {
@ExperimentalFoundationApi
override fun onCreate(savedInstanceState: Bundle?) {
super .onCreate(savedInstanceState)
setContent {
Geekscourse{
HomeScreen()
}
}
}
} |
Finally, Our UI is looking like this:
Output:
Project Link: Click Here