Advanced Layout concepts

[ad_1]

  • Layout phase: one of three phases of Compose, in which a parent layout defines the sizing and the positioning of its child elements
  • A layout: a broad, abstract term used to quickly define any UI element in Compose
  • A layout node: an abstract concept used as a visual representation of one element in the UI tree, created as a result of the Composition phase in Compose
  • Layout composable: the composable used as the core component of Compose UI. When called during Composition, it creates and adds a layout node in the Compose UI tree; the basis for all higher level layouts like Column, Row, etc.
  • layout() function — the starting point of placement, which is the second sub-step of the Layout phase and takes care of placing children in a Layout composable, right after the first sub-step of measurement
  • .layout() modifier — a modifier that wraps one single layout node and allows sizing and placing it individually, instead of this being done by its parent layout
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout() { … }
}
@Composable inline fun Layout(
content: @Composable @UiComposable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
// …
}
  • Layout — for measuring and placing 0 or more children, which accepts one composable as content
  • Layout — for leaf nodes of the UI tree with precisely 0 children, so it doesn’t have a content parameter
  • Layout — accepts a contents list for passing multiple different composables
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout(
content = content,
modifier = modifier,
measurePolicy = { measurables, constraints ->
// 1. Measurement step
// Determine sizes of components
layout(…) {
// 2. Placement step
// Determine positions of components
}
}
)
}
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout(
content = content,
modifier = modifier,
measurePolicy = { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
// Determine sizes of components
layout(…) {
// PLACEMENT SCOPE
// 2. Placement step
// Determine positions of components
}
}
)
}
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
// …
) {
Layout(
content = content,
modifier = modifier
measurePolicy = { measurables: List<Measurable>, constraints: Constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
// Determine sizes of components
}
)
}
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
// …
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
measurables.map { measurable ->
measurable.measure(constraints)
}
}
}
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
// …
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
measurables.map { measurable ->
measurable.measure(
constraints.copy(
minWidth = newWidth,
maxWidth = newWidth
)
)
}
}
}
@Composable
fun CustomLayout(
content: @Composable () -> Unit,
// …
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
val placeables = measurables.map { measurable ->
// Returns a placeable
measurable.measure(constraints)
}
}
}
@Composable
fun CustomLayout(
// …
) {
Layout(
// …
) {
// totalWidth could be the sum of all children's widths
// totalHeight could be the sum of all children's heights
layout(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
}
}
}
@Composable
fun CustomLayout(
// …
) {
Layout(
// …
) {
// …
layout(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
placeables // PLACE US! 😎
}
}
}
@Composable
fun CustomLayout(
// …
) {
Layout(
// …
) {
// …
layout(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
placeables.map { it.place(xPosition, yPosition) }
}
}
}
@Composable
fun LayoutModifierExample() {
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(40.dp)
) {
Element()
Element()
// Item below should rebel against the enforced padding and go edge to edge
Element()
Element()
}
}
Modifier.layout { measurable, constraints ->
// Measurement
val placeable = measurable.measure(...)

layout(placeable.width, placeable.height) {
// Placement
placeable.place(...)
}
}

Element(modifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(
// Resize this item's maxWidth by adding DPs to incoming constraints
maxWidth = constraints.maxWidth + 80.dp.roundToPx()
)
)
layout(placeable.width, placeable.height) {
// Place this item in the original position
placeable.place(0, 0)
}
})
BoxWithConstraints {
// maxHeight is the measurement info available only in BoxWithConstraints,
// due to the deferred Composition phase happening AFTER Layout phase measurement
if (maxHeight < 300.dp) {
SmallImage()
} else {
BigImage()
}
}

SubcompositionLayout DON’Ts

Traversing a tree with lots of UI nodes for each recomposition
@Composable
fun IntrinsicExample() {
Column() {
Text(text = "MAD")
Text(text = "Skills")
Text(text = "Layouts")
Text(text = "And Modifiers")
}
}
@Composable
fun IntrinsicExample() {
Column() {
Text(text = "MAD", Modifier.fillMaxWidth())
Text(text = "Skills", Modifier.fillMaxWidth())
Text(text = "Layouts", Modifier.fillMaxWidth())
Text(text = "And Modifiers", Modifier.fillMaxWidth())
}
}
@Composable
fun IntrinsicExample() {
Column(Modifier.width(IntrinsicSize.Max)) {
Text(text = "MAD", Modifier.fillMaxWidth())
Text(text = "Skills", Modifier.fillMaxWidth())
Text(text = "Layouts", Modifier.fillMaxWidth())
Text(text = "And Modifiers", Modifier.fillMaxWidth())
}
}
@Composable
fun IntrinsicExample() {
Column(Modifier.width(IntrinsicSize.Min) {
Text(text = "MAD", Modifier.fillMaxWidth())
Text(text = "Skills", Modifier.fillMaxWidth())
Text(text = "Layouts", Modifier.fillMaxWidth())
Text(text = "And Modifiers", Modifier.fillMaxWidth())
}
}
  • Modifier.width(IntrinsicSize.Min) — “What’s the minimum width you need to display your content properly?”
  • Modifier.width(IntrinsicSize.Max) — “What’s the maximum width you need to display your content properly?”
  • Modifier.height(IntrinsicSize.Min) — “What’s the minimum height you need to display your content properly?”
  • Modifier.height(IntrinsicSize.Max) — “What’s the maximum height you need to display your content properly?”
    Layout(
modifier = modifier,
content = content,
measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}

override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int {
// Logic for calculating custom maxIntrinsicHeight here
}

// Other intrinsics related methods have a default value,
// you can override only the methods that you need.
}
)

[ad_2]

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *