Hello, this is Android Engineer Shakil from Product Engineering Department.
Jetpack Compose is a modern, fully declarative UI toolkit for building native Android user interfaces. It comes with a lot of built-in Layouts like Rows, Columns, Boxes, etc that enables us to create a lot of different types of UI quite easily. We can also customise these Layouts quite significantly via use of the Modifier property.
However, there is maybe niche cases where the built in Components with Modifiers may not be enough. What to do then. Well, Custom layouts to the rescue.
Well at first what are Layouts? Well, you can think of layouts as just containers for UI elements, where these UI elements are Composables. Basically, a Layouts’s job is to determine how all its children are positioned and displayed on the Screen.
Now, let us try to create the following Layout where the UI elements are arranged like steps on a stair.
Creating our custom Stairs layout
It would be very difficult to achieve this with a simple Column Layout, so let’s make our own Layout called Stairs using the following code.
@Composable private fun Stairs( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { Layout( modifier = modifier, measurePolicy = { measurables, constraints -> val layoutMaxWidth = constraints.maxWidth val numberOfChildren = measurable.size val maxWidth = layoutMaxWidth / numberOfChildren val placeables = measurables.map { it.measure( constraints = constraints.copy(maxWidth = maxWidth) ) } var x = 0 var y = 0 layout( width = constraints.maxWidth, height = constraints.maxHeight ) { placeables.forEach { placable -> placable.place(x, y) x += placable.width y += placable.height } } }, content = content ) }
As you can see, in the Layout function we are passing in 3 arguments, modifier, measurePolicy and content. The content passed is a Collection of all the children Composable that are going to be in our Stairs custom Layout. The measurePolicy is a lambda function that takes in a list of measurables and constraints. The constraints hold the minimum and maximum widths and heights the children can be. The measurables is a list of children in our Layout which once measured for size gives us placables which we can place on the screen.
In our Stairs example, while measuring the children we are limiting the maximum width of the children such that available maximum width of entire layout is equally divided amongst all its children by passing in a constraint with maxWidth of layoutMaxWidth / numberOfChildren.
Then we call the layout function where we place all the Children. In the screen the top left corner has the coordinate (0, 0) and x increases as we go right and y increases as we go down. So, while placing all the children on the screen if we increment the x and y coordinate of the next child by the width and height of the previous child, we get a Stairs like placement.
To see our Stairs Custom Layout in action, we can us the following code.
@Composable fun StairsPreview() { MaterialTheme { Stairs { repeat(12) { Box( modifier = Modifier .fillMaxWidth() // giving each children a random height .height(Random.nextInt(30, 50).dp) // giving each children a random color .background(Color(Random.nextLong(0xFFFFFFFF))) ) } } } }
Conclusion
That’s it for this blog post. There are a lot more things we can achieve with Custom layouts in Jetpack compose to meet unique needs for our products. For more you can refer to the documentation for Custom layouts.