iTranslated by AI
Building Layouts with Jetpack Compose
A record of my attempt to replicate the layout design from this link as part of my Jetpack Compose training.
Goal

LazyColumn
@Composable
fun TestContent() {
val items = listOf("TAB", "PROFILE", "TITLE", "ITEM", "ITEM", "ITEM", "ITEM", "ITEM", "ITEM")
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(items, itemContent = { item ->
when (item) {
"TAB" -> TextTabs()
"PROFILE" -> Profile()
"TITLE" -> TitleRow()
"ITEM" -> ItemRow()
}
})
}
}
Displays content with scrolling.
It serves as a replacement for RecyclerView.
ToolBar

Displays the hamburger icon, title, and + button.
topBar = {
TopAppBar(
title = { Text("Title") },
navigationIcon = {
IconButton(onClick = {}) {
Icon(painterResource(sobaya.app.resources.R.drawable.ic_menu_black_24dp), "menu")
}
},
actions = {
IconButton(onClick = {}) {
Icon(Icons.Filled.Add, contentDescription = "")
}
}
)
}
Specified in the topBar of the Scaffold.
I placed the hamburger icon in navigationIcon and the + button in actions.
DrawerContent

It doesn't have any content, but I prepared it anyway.
drawerContent = { Text(text = "drawerContent") }
Specified in the drawerContent of the Scaffold.
Tab

@Composable
fun TextTabs() {
var tabIndex by remember { mutableStateOf(0) }
val tabData = listOf(
"Soba",
"Udon"
)
TabRow(selectedTabIndex = tabIndex) {
tabData.forEachIndexed { index, text ->
Tab(selected = tabIndex == index, onClick = {
tabIndex = index
}, text = {
Text(text = text)
})
}
}
}
It took some time since it was my first time using it, but from next time on, copying and pasting seems like the way to go.
ConstraintLayout

ConstraintLayout(modifier = Modifier
.fillMaxWidth()
.height(200.dp)) {
val (iconImage, userName, mail, iconD, point, help) = createRefs()
Image(
painter = rememberImagePainter("https://avatars.githubusercontent.com/u/45986582?v=4"),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
)
Image(
painter = rememberImagePainter(
data = "https://avatars.githubusercontent.com/u/45986582?v=4",
builder = {
transformations(CircleCropTransformation())
}
),
contentDescription = null,
modifier = Modifier
.size(64.dp)
.constrainAs(iconImage) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start, margin = 16.dp)
},
)
Text(
text = "Sobaya-0141",
color = Color.White,
fontSize = 24.sp,
modifier = Modifier.constrainAs(userName) {
start.linkTo(mail.start)
bottom.linkTo(mail.top, margin = 16.dp)
}
)
Text(
text = "soba.ha.kenkou@gmail.com",
color = Color.LightGray,
fontSize = 16.sp,
modifier = Modifier.constrainAs(mail) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(iconImage.end, margin = 16.dp)
}
)
Icon(
imageVector = Icons.Default.Star,
contentDescription = "star",
modifier = Modifier.constrainAs(iconD) {
top.linkTo(mail.bottom, margin = 16.dp)
start.linkTo(mail.start)
}
)
Text(
text = "0141 points",
color = Color.White,
modifier = Modifier.constrainAs(point) {
top.linkTo(iconD.top)
start.linkTo(iconD.end, margin = 16.dp)
}
)
Icon(
imageVector = Icons.Default.Info,
contentDescription = "info",
modifier = Modifier.constrainAs(help) {
top.linkTo(iconD.top)
start.linkTo(point.end, margin = 16.dp)
}
)
}
I was able to write it with a feeling close to XML, so once I figured out the first two or so, the rest was finished in an instant.
By declaring what to place with
val (iconImage, userName, mail, iconD, point, help) = createRefs()
and declaring which reference corresponds to the element like
modifier = Modifier.constrainAs(iconD)
top.linkTo(iconD.top)
start.linkTo(point.end, margin = 16.dp)
you specify how and with whom you want to connect.
Personally, I might like it more than XML.
Others (The part I struggled with the most)

Box(modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)) {
Box(modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.background(Color.Gray)) {
Box(modifier = Modifier
.fillMaxWidth()
.padding(start = 1.dp, end = 1.dp, bottom = 1.dp)
.background(Color.White)
) {
Column(
Modifier
.padding(start = 84.dp)
.fillMaxWidth()) {
Text(
text = "Sobaya",
color = Color.Black,
fontSize = 18.sp,
modifier = Modifier
.padding(top = 16.dp)
)
Text(
text = "All-you-can-eat",
color = Color.Gray,
fontSize = 14.sp,
modifier = Modifier
.padding(end = 16.dp, top = 8.dp)
)
Text(
text = "All-you-can-drink",
color = Color.Gray,
fontSize = 14.sp,
modifier = Modifier
.padding(end = 16.dp, top = 8.dp)
)
Text(
text = "No toilet",
color = Color.Gray,
fontSize = 14.sp,
modifier = Modifier
.padding(end = 16.dp, top = 8.dp, bottom = 8.dp)
)
}
}
}
Card(modifier = Modifier
.size(84.dp)
.padding(start = 8.dp)
.align(Alignment.CenterStart)) {
Image(
painter = rememberImagePainter(
data = "https://avatars.githubusercontent.com/u/45986582?v=4",
),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
}
Rounded corners for images
Card(modifier = Modifier
.size(84.dp)
.padding(start = 8.dp)
.align(Alignment.CenterStart)) {
Image(
painter = rememberImagePainter(
data = "https://avatars.githubusercontent.com/u/45986582?v=4",
),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
I thought I could transform it with Coil, but there was no radius specification, so I solved it by specifying the maximum size on a Card along with ContentScale.Crop.
Making the background light gray with a dark gray border and a white background
Box(modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)) {
Box(modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.background(Color.Gray)) {
Box(modifier = Modifier
.fillMaxWidth()
.padding(start = 1.dp, end = 1.dp, bottom = 1.dp)
.background(Color.White)
) {
There is a border option in the modifier, and while it is possible to draw lines, it draws lines on all sides.
Therefore, when used for continuous items like in this case, the bottom line of the first item and the top line of the second item would both be drawn, making the divider twice as thick, so it couldn't be used.
I had assumed I could specify a drawable/xml for the background, but that was also not possible.
So, similar to drawing lines on specific sides in XML:
- Prepare a rectangle filled with the color of the line you want to draw.
- Prepare a rectangle with padding only where you want the line to appear.
This is how I drew the lines.
Furthermore, since I needed to specify light gray as the background this time:
- LightGray fill
- Gray fill on top of that (border)
- White fill on top of that (content area)
This is the structure.
Finally
I wonder why the scrolling is so heavy??
I'd appreciate it if anyone could let me know if there's a more efficient way to write this.
References
Discussion