What You'll Learn
This article explains SubcomposeLayout (measure results → control composition, dynamic sizing, custom layouts, performance notes).
What is SubcomposeLayout
Normal Layout measures and composes children simultaneously. SubcomposeLayout can use measurement results to control composition of other children.
@Composable
fun MeasureThenDecide(
mainContent: @Composable () -> Unit,
dependentContent: @Composable (IntSize) -> Unit
) {
SubcomposeLayout { constraints ->
val mainPlaceables = subcompose("main") { mainContent() }
.map { it.measure(constraints) }
val mainSize = IntSize(
mainPlaceables.maxOf { it.width },
mainPlaceables.sumOf { it.height }
)
val dependentPlaceables = subcompose("dependent") { dependentContent(mainSize) }
.map { it.measure(constraints) }
val totalHeight = mainSize.height + dependentPlaceables.sumOf { it.height }
layout(constraints.maxWidth, totalHeight) {
var y = 0
mainPlaceables.forEach { it.placeRelative(0, y); y += it.height }
dependentPlaceables.forEach { it.placeRelative(0, y); y += it.height }
}
}
}
Equal Height Columns
@Composable
fun EqualHeightColumns(
leftContent: @Composable () -> Unit,
rightContent: @Composable () -> Unit
) {
SubcomposeLayout { constraints ->
val halfWidth = constraints.maxWidth / 2
val halfConstraints = constraints.copy(maxWidth = halfWidth)
val leftPlaceables = subcompose("left") { leftContent() }
.map { it.measure(halfConstraints) }
val rightPlaceables = subcompose("right") { rightContent() }
.map { it.measure(halfConstraints) }
val maxHeight = maxOf(
leftPlaceables.maxOfOrNull { it.height } ?: 0,
rightPlaceables.maxOfOrNull { it.height } ?: 0
)
layout(constraints.maxWidth, maxHeight) {
leftPlaceables.forEach { it.placeRelative(0, 0) }
rightPlaceables.forEach { it.placeRelative(halfWidth, 0) }
}
}
}
Text Width Matching Indicator
@Composable
fun TextWithMatchingIndicator(text: String, isSelected: Boolean) {
SubcomposeLayout(Modifier.padding(horizontal = 16.dp)) { constraints ->
val textPlaceable = subcompose("text") {
Text(
text,
style = MaterialTheme.typography.titleMedium,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
)
}.first().measure(constraints)
val indicatorPlaceable = subcompose("indicator") {
if (isSelected) {
Box(
Modifier
.width(with(LocalDensity.current) { textPlaceable.width.toDp() })
.height(3.dp)
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(1.5.dp))
)
}
}.firstOrNull()?.measure(constraints)
val totalHeight = textPlaceable.height + (indicatorPlaceable?.height ?: 0)
layout(textPlaceable.width, totalHeight) {
textPlaceable.placeRelative(0, 0)
indicatorPlaceable?.placeRelative(0, textPlaceable.height)
}
}
}
Performance Notes
// ❌ SubcomposeLayout is heavier than normal Layout
// Use Modifier for simple size specifications
// ✅ Use Modifier when possible
Box(Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
Row {
Column(Modifier.weight(1f).fillMaxHeight()) { /* left */ }
Column(Modifier.weight(1f).fillMaxHeight()) { /* right */ }
}
}
// Use SubcomposeLayout only when:
// - Need to compose child B based on measurement of child A
// - Need to dynamically change number of Composables
Summary
| Feature | API |
|---|---|
| Dynamic composition | SubcomposeLayout |
| Measure child | subcompose().measure() |
| Positioning | layout { placeRelative() } |
| Simple alternative | IntrinsicSize |
- Use
SubcomposeLayoutfor 2-stage measurement→composition control - Perfect for equal-height columns, text-width dependent layouts
- Prefer normal
LayoutorIntrinsicSizewhen they work - Optimize recomposition with
subcomposeslot IDs
8 production-ready Android app templates (custom layouts included) are available.
Browse templates → Gumroad
Related articles:
- Custom Layout
- ConstraintLayout
- Canvas drawing
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)