This episode builds your own components: custom layout composables with MeasurePolicy, advanced modifiers and pointer input, reusable UI primitives, and developing complex components from scratch.

Column, Row, and Box cover most needs, but sometimes you need a layout that doesn't exist in the library: flow text, a staggered grid, or bars that fill space with their own rules. That's where custom layouts come in.
Episode 18 opens the lower layer of Compose: the Layout API to measure and place children, Modifier.layout and Modifier.drawBehind for effects, and pointer input for interaction. With these you can build reusable UI primitives and complex components.
Episode 18 covers custom layouts, advanced modifiers, pointer input, and component development.
The Layout composable accepts content and a MeasurePolicy responsible for measuring and placing children:
@Composable
fun FlowRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
var x = 0
var y = 0
var maxBaris = 0
val placeables = measurables.map { measurable ->
val placeable = measurable.measure(constraints.copy(minWidth = 0))
if (x + placeable.width > constraints.maxWidth) {
x = 0
y += maxBaris
maxBaris = 0
}
val hasil = PlaceablePos(x, y, placeable)
maxBaris = maxOf(maxBaris, placeable.height)
x += placeable.width
hasil
}
layout(
width = constraints.maxWidth,
height = y + maxBaris
) {
placeables.forEach { pos ->
pos.placeable.placeRelative(pos.x, pos.y)
}
}
}
}measurable.measure(constraints) measures each child, then layout(width, height) sets the overall size and placeRelative(x, y) places the children. This pattern is the engine behind many layout libraries. To observe a custom layout result on a device, build and install the application with ./gradlew :app:installDebug.
Use a custom layout when the behavior can't be expressed with built-in composables: special text wrapping, unusual weight distribution, or layouts that read data first. If Column and Row are enough, don't force it.
Custom modifiers can change the size and position of the modified element:
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
placeable.placeRelative(placeable.width / 2, 0)
}
}Modifier.layout moves the element half its width to the right. This modifier gives fine-grained per-element control — the opposite of Layout, which arranges many children.
For visual effects, drawBehind draws behind the content, and clickable or pointerInput handles interaction:
Modifier
.drawBehind {
drawCircle(
color = HijauMuda,
radius = 8.dp.toPx()
)
}
.pointerInput(Unit) {
detectTapGestures { posisi ->
onTitikDiklik(posisi)
}
}drawCircle draws a circle behind the content, and detectTapGestures forwards the tap coordinates. Combining draw and pointer is the foundation of interactive graphics.
Combine the primitives above into a complete component — for example a draggable card with a status indicator:
@Composable
fun KartuDrag(
onSeret: (Float) -> Unit,
modifier: Modifier = Modifier
) {
var offsetY by remember { mutableFloatStateOf(0f) }
Card(
modifier = modifier
.offset { IntOffset(0, offsetY.roundToInt()) }
.pointerInput(Unit) {
detectDragGestures { change, _ ->
change.consume()
offsetY += change.positionChange().y
onSeret(offsetY)
}
}
) {
Text("Seret saya", Modifier.padding(16.dp))
}
}offsetY stores the drag position, detectDragGestures updates it, and offset applies it. change.consume() marks the gesture as handled. Components like this can be reused across the entire application.
Wrap custom components into design system primitives: Badge, RatingBar, custom TabStrip. By accepting Modifier as a parameter, every primitive stays composable and testable like any other component.
Episode 18 opened the lower layer of Compose: the Layout composable with MeasurePolicy for custom layouts, Modifier.layout and drawBehind for per-element control, pointer input for interaction, and building reusable complex components and design system primitives.
Key takeaways:
Layout gives control over measuring and placing children.In episode 19 we will discuss operational readiness and runbooks — handling crashes and regressions, monitoring application and UI health, release processes and rollout strategies, and maintaining consistent design quality.