Jetpack Compose is a new toolkit for building native Android interfaces. It simplifies and speeds up interface development on Android, using less code, powerful tools, and an intuitive Kotlin API to quickly bring apps to life. Compose uses a new component , Composable, to lay out the interface, and Modifier to configure the Composable.
This article walks you through the composition layout model powered by composables and modifiers, and dives into how it works behind it and what they do, giving you a better understanding of how the layouts and modifiers you use work, and how And when to build custom layouts to achieve designs that meet exact application needs.
If you prefer to understand the content of this article through video, please click here to watch.
layout model
The goal of the Compose layout system is to provide easy-to-create layouts, especially custom layouts . This requires the layout system to be powerful enough to allow developers to create any layout the application needs, and to make the layout perform well. Next, let's look at how Compose's layout model achieves these goals.
Jetpack Compose can convert state into interface. This process is divided into three steps: composition, layout, and drawing. The composition phase executes composable functions that can generate an interface, thereby creating an interface tree. For example, the SearchResult function in the following figure will generate the corresponding interface tree:
△ Combinable functions generate corresponding interface trees
Composables can contain logic and control flow, so different interface trees can be generated based on different states. During the layout phase, Compose traverses the interface tree, measures the various parts of the interface, and places each part in the screen's 2D space. That is, each node determines its respective width, height, and x and y coordinates. During the draw phase, Compose will traverse the interface tree again and render all elements.
This article will delve into the layout phase. The layout phase is subdivided into two phases: measurement and placement. This is equivalent to onMeasure and onLayout in the View system. But in Compose, these two phases intersect, so we think of it as a layout phase. The process of laying out each node in the interface tree is divided into three steps: each node must measure all its own child nodes, determine its own size, and then place its child nodes. In the following example, the entire interface tree can be laid out in a single pass.
△ Layout process
The process is briefly described as follows:
- measure the root layout Row;
- Row measures its first child node Image;
- Since Image is a leaf node with no children, it measures and reports its own dimensions, and returns instructions on how to place its children. Image's leaf nodes are usually empty nodes, but all layouts return these placement instructions while setting their dimensions;
- Row measures its second child node Column;
- Column measures its child nodes, first measure the first child node Text;
- Text measures and reports its dimensions and placement instructions;
- Column measures the second child node Text;
- Text measures and reports its dimensions and placement instructions;
- After measuring its child nodes, Column can determine its own size and placement logic;
- A Row determines its own size and placement instructions based on the measurements of all its child nodes.
After the dimensions of all elements are measured, the UI tree is traversed again and all placement instructions are executed during the placement phase.
Layout Composables
Now that we've seen the steps involved in this process, let's take a look at how it's implemented. Let's first look at the composition phase. We use higher-level composable items such as Row, Column, and Text to represent the interface tree. Each high-level composable item is actually constructed from low-level composable items. Taking Text as an example, it can be found that it consists of several lower-level basic building blocks, and these composables all contain one or more Layout composables.
△ Each composable item contains one or more Layouts
The Layout composable is the fundamental building block of the Compose interface, which generates LayoutNodes. In Compose, the interface tree, or composition, is a tree of LayoutNodes. The following is the function signature of the Layout composable:
@Composable fun Layout( content: @Composable () -> Unit, modifier: Modifier = Modifier, measurePolicy: MeasurePolicy ) { … }
△ Layout composable function signature
Among them, content is a slot that can accommodate any sub-composable items. For layout needs, content will also contain sub-Layouts. The modifier specified by the modifier parameter will be applied to the layout, which is described in detail below. The measurePolicy parameter is of type MeasurePolicy, a functional interface that specifies how the layout measures and places items. In general, to implement the behavior of a custom layout, you would implement this functional interface in your code:
@Composable fun MyCustomLayout( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content ) { measurables: List<Measurable>, constraints: Constraints -> // TODO 测量和放置项目} }
△ Implement the MeasurePolicy functional interface
In the MyCustomLayout composable, we call the Layout function and provide the MeasurePolicy as a parameter in the form of a Trailing Lambda to implement the desired measure function. This function accepts a Constraints object to tell the Layout its size constraints. Constraints is a simple class that limits the maximum and minimum width and height of a Layout:
class Constraints { val minWidth: Int val maxWidth: Int val minHeight: Int val maxHeight: Int }
△ Constraints
The measure function also accepts a List<Measurable> as a parameter, which represents the child elements passed in. The Measurable type exposes functions for measuring items. As mentioned earlier, laying out each element requires three steps: each element must measure all its children, determine its own size, and place its children. Its code implementation is as follows:
@Composable fun MyCustomLayout( content: @Composable () -> Unit, modifier: Modifier = Modifier ) { Layout( modifier = modifier, content = content ) { measurables: List<Measurable>, constraints: Constraints -> // placeables 是经过测量的子元素,它拥有自身的尺寸值val placeables = measurables.map { measurable -> // 测量所有子元素,这里不编写任何自定义测量逻辑,只是简单地// 调用Measurable 的measure 函数并传入constraints measurable.measure(constraints) } val width = // 根据placeables 计算得出val height = // 根据placeables 计算得出// 报告所需的尺寸layout (width, height) { placeables.foreach { placeable -> // 通过遍历将每个项目放置到最终的预期位置placeable.place( x = … y = … ) } } } }
△ Code example for laying out each element
Placeable's place function is used in the above code, and it also has a placeRelative function that can be used in right-to-left language settings, which automatically mirrors the coordinates horizontally when used.
Note that the API is designed to prevent you from trying to place unmeasured elements, the place function only works with Placeable, which is the return value of the measure function. In the View system, when you call onMeasure and onLayout is up to you, and the order of calls is not enforced, but this can create some subtle bugs and differences in behavior.
Custom layout example
MyColumn example
△ Column
Compose provides a Column component for arranging elements vertically. To understand how this component works behind the scenes and how it uses Layout composables, let's implement a Column of our own. Let's name it MyColumn for the time being, and its implementation code is as follows:
@Composable fun MyColumn( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content ) { measurables, constraints -> // 测量每个项目并将其转换为Placeable val placeables = measurables.map { measurable -> measurable.measure(constraints) } // Column 的高度是所有项目所测得高度之和val height = placeables.sumOf { it.height } // Column 的宽度则为内部所含最宽项目的宽度val width = placeables.maxOf { it.width } // 报告所需的尺寸layout (width, height) { // 通过跟踪y 坐标放置每个项目var y = 0 placeables.forEach { placeable -> placeable.placeRelative(x = 0, y = y) // 按照所放置项目的高度增加y 坐标值y += placeable.height } } } }
△ Custom Column
VerticalGrid example
△ VerticalGrid
Let's look at another example: building a regular mesh. Part of the code is implemented as follows:
@Composable fun VerticalGrid( modifier: Modifier = Modifier, columns: Int = 2, content: @Composable () -> Unit ) { Layout( content = content, modifier = modifier ) { measurables, constraints -> val itemWidth = constraints.maxWidth / columns // 通过copy 函数保留传递下来的高度约束,但设置确定的宽度约束val itemConstraints = constraints.copy ( minWidth = itemWidth, maxWidth = itemWidth, ) // 使用这些约束测量每个项目并将其转换为Placeable val placeables = measurables.map { it.measure(itemConstraints) } … } }
△ Custom VerticalGrid
In this example, we create a new constraint with the copy function. This concept of creating new constraints for child nodes is how custom measurement logic is implemented. The ability to create different constraints to measure child nodes is the key to this model. There is no negotiation mechanism between parent nodes and child nodes. The parent node will pass the size range of its allowable child nodes in the form of Constraints, as long as the child node is from this range. With its dimensions selected, the parent node must accept and process the child node.
The advantage of this design is that we can measure the entire interface tree in a single pass, and multiple measurement loops are prohibited. This is a problem in the View system, where nested structures perform multiple measurements that can double the number of measurements on a leaf view, and Compose is designed to prevent this from happening. In fact, if you measure an item twice, Compose throws an exception:
△ Compose will throw an exception when measuring an item repeatedly
Layout animation example
With stronger performance guarantees, Compose offers new possibilities, such as adding animations to layouts. Layout composable can create not only generic layouts, but also specialized layouts that fit your application design needs. Take custom bottom navigation in the Jetsnack app as an example, in this design, if an item is selected, the label is displayed; if it is not selected, only the icon is displayed. Also, the design needs to animate the size and position of the item based on the current selection state.
△ Custom bottom navigation in the Jetsnack app
We can implement this design using a custom layout, giving precise control over the animation of layout changes:
@Composable fun BottomNavItem( icon: @Composable BoxScope.() -> Unit, text: @Composable BoxScope.() -> Unit, @FloatRange(from = 0.0, to = 1.0) animationProgress: Float ) { Layout( content = { // 将icon 和text 包裹在Box 中// 这种做法能让我们为每个项目设置layoutId Box( modifier = Modifier.layoutId(“icon”) content = icon ) Box( modifier = Modifier.layoutId(“text”) content = text ) } ) { measurables, constraints -> // 通过layoutId 识别对应的Measurable,比依赖项目的顺序更可靠val iconPlaceable = measurables.first {it.layoutId == “icon” }.measure(constraints) val textPlaceable = measurables.first {it.layoutId == “text” }.measure(constraints) // 将放置逻辑提取到另一个函数中以提高代码可读性placeTextAndIcon( textPlaceable, iconPlaceable, constraints.maxWidth, constraints.maxHeight, animationProgress ) } } fun MeasureScope.placeTextAndIcon( textPlaceable: Placeable, iconPlaceable: Placeable, width: Int, height: Int, @FloatRange(from = 0.0, to = 1.0) animationProgress: Float ): MeasureResult { // 根据动画进度值放置文本和图标val iconY = (height - iconPlaceable.height) / 2 val textY = (height - textPlaceable.height) / 2 val textWidth = textPlaceable.width * animationProgress val iconX = (width - textWidth - iconPlaceable.width) / 2 val textX = iconX + iconPlaceable.width return layout(width, height) { iconPlaceable.placeRelative(iconX.toInt(), iconY) if (animationProgress != 0f) { textPlaceable.placeRelative(textX.toInt(), textY) } } }
△ Customize bottom navigation
When to use custom layouts
Hopefully the above examples have helped you understand how custom layouts work and how these layouts can be applied. Standard layouts are powerful and flexible, but they also need to be adapted to many use cases. Sometimes it may be more appropriate to use a custom layout if you know your specific implementation needs.
We recommend using custom layouts when you encounter the following scenarios:
- Designs that are difficult to achieve with standard layouts. Although it is possible to build most interfaces with enough Row and Column, this implementation is sometimes difficult to maintain and upgrade;
- Requires very precise control of measurement and placement logic;
- Layout animation needs to be implemented. We are working on a new API to animate placement, which may be possible in the future without having to write your own layout;
- Full control over performance is required. This is covered in detail below.
modifier
So far, we've seen Layout composables and how to build custom layouts. If you've built an interface with Compose, you know that modifiers play an important role in layout, configuration size, and position. As you can see from the previous example, the Layout composable accepts a modifier chain as a parameter. Modifiers decorate the elements they are attached to and can participate in measurement and placement before the layout's own measurement and placement operations. Next let's see how it works.
There are many different types of modifiers that can affect different behaviors, such as DrawModifier, PointerInputModifier, and FocusModifier. In this article we will focus on the LayoutModifier, which provides a measure method that works basically the same as Layout composables, except that it only acts on a single Measurable instead of a List<Measurable> , this is because the modifier is applied to a single item. In the measure method, modifiers can modify constraints or implement custom placement logic, just like layout. This means that you don't always need to write custom layouts, if you only want to perform actions on a single item, you can use modifiers instead.
Taking the padding modifier as an example, this factory function builds on the modifier chain and creates a PaddingModifier object that captures the desired padding value.
fun Modifier.padding(all: Dp) = this.then(PaddingModifier( start = all, top = all, end = all, bottom = all ) ) private class PaddingModifier( val start: Dp = 0.dp, val top: Dp = 0.dp, val end: Dp = 0.dp, val bottom: Dp = 0.dp ) : LayoutModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val horizontal = start.roundToPx() + end.roundToPx() val vertical = top.roundToPx() + bottom.roundToPx() // 按padding 尺寸收缩外部约束来修改测量val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { // 按所需的padding 执行偏移以放置内容placeable.placeRelative(start.roundToPx(), top.roundToPx()) } } }
△ Implementation of padding modifier
In addition to implementing measurement by overriding the measure method in the above example, you can also use Modifier.layout to add custom measurement and placement logic to any composable item directly through the modifier chain without creating a custom layout, as follows shown:
Box(Modifier .background(Color.Gray) .layout { measurable, constraints -> // 通过修饰符在竖直方向添加50 像素padding 的示例val padding = 50 val placeable = measurable.measure(constraints.offset(vertical = -padding)) layout(placeable.width, placeable.height + padding) { placeable.placeRelative(0, padding) } } ) { Box(Modifier.fillMaxSize().background(Color.DarkGray)) }
△ Use Modifier.layout to implement layout
Although Layout accepts a single Modifier parameter, this parameter establishes a chain of modifiers that are applied in order. Let's take an example to see how it interacts with the layout model. We will analyze the effect of the modifier in the image below and how it works:
△ Example of effect of modifier chain
First, we set the size of the Box and draw it, but the Box is placed in the upper left corner of the parent layout, we can use the wrapContentSize modifier to center the Box. wrapContentSize allows the content to measure its desired size, and then use the align parameter to place the content. The default value of the align parameter is Center, so this parameter can be omitted. But we found that the Box is still in the upper left corner. This is because most layouts will adaptively resize based on their content, and we need to make the measurement dimensions take up the entire space in order to center the Box within the space. Therefore, we add the fillMaxSize layout modifier in front of wrapContentSize to achieve this effect.
△ Modifier chain application process
Let's take a look at how these modifiers achieve this effect. You can use the following animation to help understand the process:
△ How Modifier Chains Work
Assuming this Box is to be placed in a container with a maximum size of 200*300 pixels, the container will pass the corresponding constraints into the first modifier of the modifier chain. fillMaxSize actually creates a new set of constraints and sets the maximum and minimum width and height equal to the maximum width and height passed in to fill to the maximum, in this case 200*300 pixels. These constraints are passed down the modifier chain to measure the next element, the wrapContentSize modifier takes these parameters and it creates new constraints to relax the constraints on the incoming constraints so that the content measures its desired size, which is 0 width -200, high 0-300. This only looks like the inverse of the fillMax step, but note that we are using this modifier to center the item, not to resize the item. These constraints are passed along the modifier chain to the size modifier, which creates size-specific constraints to measure the item, specifying that the size should be exactly 50*50. Finally, these constraints are passed to the Box's layout, which performs the measurement and returns the parsed size (50*50) to the modifier chain, the size modifier thus also parses its size as 50*50, and creates placement instructions accordingly . Then wrapContent parses its size and creates a drop directive to center the content. Because the wrapContent modifier knows that its dimensions are 200*300 and the next element's dimensions are 50*50, a placement directive is created with center alignment to center the content. Finally, fillMaxSize resolves its dimensions and performs a placement operation.
Modifier chains work much like layout trees, except that each modifier has only one child, the next element in the chain. Constraints are passed down for subsequent elements to measure their own dimensions, then return the parsed dimensions and create placement directives. This example also illustrates the importance of modifier order . By combining functions using modifiers, you can easily combine different measurement and layout strategies.
Advanced Features
Next, we'll cover some advanced features of the layout model that you won't always need, but can help you build more advanced features.
Intrinsic Measurement
As mentioned earlier, Compose uses a single distribution system. This statement is not entirely true, layout is not always done in a single pass, and sometimes we also need to know about the size of the child nodes to finalize the constraints.
Take pop-up menus as an example. Suppose there is a Column containing five menu items, as shown in the figure below, its display is basically normal, but as you can see, the size of each menu item is different.
△ Menu items are not the same size
It's easy to think that it's enough to make each menu item occupy the maximum size allowed:
△ Each menu item occupies the maximum size allowed
But this didn't solve the problem completely, because the menu window would expand to its maximum size. A valid workaround is to use the maximum intrinsic width to determine the size:
△ Use the maximum inherent width to determine the size
This determines that Column will try to provide the required space for each child node, and for Text, its width is the width required to render the entire text on a single line. After the intrinsic size is determined, the values are used to set the size of the Column, and the child nodes can then fill the width of the Column.
What happens if the minimum value is used instead of the maximum value?
△ Use the minimum inherent width to determine the size
It will determine the minimum size of the child nodes that Column will use, and the minimum intrinsic width of Text is the width of one word per line. So we end up with a word-wrap menu.
For more information on intrinsic properties measurement, see the "Intrinsic Properties" section in the Layout Codelab in Jetpack Compose .
ParentData
The modifiers we've seen so far have been generic modifiers, that is, they can be applied to any composable item. Sometimes, some behavior provided by your layout may require some information from child nodes, which is where the ParentDataModifier is used .
Let's go back to the previous example of centering the blue Box in the parent node. This time, we put this Box inside another Box. The contents of a Box are laid out within a receiver scope called a BoxScope. BoxScope defines modifiers that are only available inside Box, it provides a modifier called Align. This modifier is exactly what we want to apply to the blue Box. So if we know the blue Box is inside another Box, we can use the Align modifier to position it instead.
△ In BoxScope, you can use the Align modifier to locate the content
Align is a ParentDataModifier and not the kind of layout modifier we saw earlier, because it just passes some information to its parent, so if it's not in a Box, the modifier isn't available. The information it contains will be provided to the parent Box for setting the child layout.
You can also write a ParentDataModifier for your own custom layout, allowing the child node to tell the parent some information for the parent to use when laying out.
Alignment Lines
We can use snaplines to set alignment based on criteria other than the top, bottom, or center of the layout. The most commonly used alignment line is the text baseline. Suppose you need to implement such a design:
△ Need to achieve icon and text alignment in the design drawing
We can naturally think of doing it like this:
Row { Icon(modifier = Modifier .size(10. dp) .align(Alignment.CenterVertically) ) Text(modifier = Modifier .padding(start = 8.dp) .align(Alignment.CenterVertically) ) }
△ Problematic alignment implementation
If you look closely, you will see that the icons are not aligned to the baseline of the text like the design.
△ The icon and text are centered, and the bottom of the icon does not fall on the text baseline
We can fix it with the following code:
Row { Icon(modifier = Modifier .size(10. dp) .alignBy { it.measuredHeight } ) Text(modifier = Modifier .padding(start = 8.dp) .alignByBaseline() ) }
△ Correct alignment is achieved
First, use the alignByBaseline modifier on Text. While the icon has neither a baseline nor other alignment lines, we can use the alignBy modifier to align the icon anywhere we want. In this case, we know that the bottom of the icon is the target position for alignment, so we align the bottom of the icon. In the end, the desired effect is achieved:
△ The bottom of the icon is perfectly aligned with the baseline of the text
Since the snap function goes through the parent node, when dealing with nested snaps, just set the parent node's snap line and it will get the corresponding value from the child node. As shown in the following example:
△ Nested layout without alignment
△ Set the alignment line through the parent node
You can even create your own custom alignment in a custom layout, allowing other composable items to snap to it.
BoxWithConstraints
BoxWithConstraints is a powerful and useful layout. In composition, we can conditionally use logic and control flow to choose what to display, however, sometimes we may want to decide what to lay out based on the amount of space available.
From the previous article, we know that size information is not available until the layout stage, that is, this information generally cannot be used in the composition stage to decide what to display. This is where BoxWithConstraints comes in handy, it's similar to Box, but it defers the composition of the content until the layout phase, when the layout information is already available. The content in BoxWithConstraints is laid out in the receiver scope through which constraints determined during the layout phase are exposed as pixel or DP values.
@Composable fun BoxWithConstraints( ... content: @Composable BoxWithConstraintsScope.() -> Unit ) // BoxWithConstraintsScope 公开布局阶段确定的约束interface BoxWithConstraintsScope : BoxScope { val constraints: Constraints val minWidth: Dp val maxWidth: Dp val minHeight: Dp val maxHeight: Dp }
△ BoxWithConstraints and BoxWithConstraintsScope
The content inside it can use these constraints to choose what to combine. For example, to choose a different rendering based on the maximum width:
@Composable fun MyApp(...) { BoxWithConstraints() { // this: BoxWithConstraintsScope when { maxWidth < 400.dp -> CompactLayout() maxWidth < 800.dp -> MediumLayout() else -> LargeLayout() } } }
△ Select different layouts according to the maximum width in BoxWithConstraintsScope
performance
We describe how a single-place layout model prevents excessive time spent on measurement or placement, and we demonstrate two distinct sub-phases of the layout phase: measure and place. Now, we'll cover the performance-related stuff.
Try to avoid restructuring
The design effect of the single-distribution model is that any modification that affects only the placement of the item and not the measurement can be performed independently. Take Jetsnack as an example:
△ Coordinated scrolling of product detail pages in the Jetsnack app
This product detail page contains a coordinated scrolling effect, where some elements on the page move or scale according to the scrolling action. Note the header area, which scrolls with the page content and ends up anchored to the top of the screen.
@Composable fun SnackDetail(...) { Box { val scroll = rememberScrollState(0) Body(scroll) Title(scroll = scroll.value) ... } } @Composable fun Body(scroll: ScrollState) { Column(modifier = Modifier.verticalScroll(scroll)) { … } }
△ Rough realization of the details page
To achieve this, we stack the different elements as separate composables in a Box, extract the scroll state and pass it into the Body component. The body is set up with the scroll state to enable the content to scroll vertically. Scroll position can be observed in other components like Title, and how we observe it can have a performance impact. For example, using the most straightforward implementation, simply use the scroll value to offset the content:
@Composable fun Title(scroll: Int) { Column( modifier = Modifier.offset(scroll) ) { … } }
△ Simply use the scroll value to offset the content of the Title
The problem with this approach is that scrolling is an observable state value, and the scope in which the value is read dictates what Compose needs to re-do when the state changes. In this example, we want to read the scroll offset value in the composition and use it to create an offset modifier. Whenever the scroll offset value changes, the Title component needs to be reassembled, and a new offset modifier needs to be created and executed. Since the scroll state is read from the composition, any changes result in a reorganization, which requires two subsequent phases, layout and drawing.
However, we're not changing what's displayed, we're changing the location of the content. We can also improve the efficiency even more by modifying the implementation so that instead of accepting the original scroll position, we pass a function that provides the scroll position:
@Composable fun Title(scrollProvider: () -> Int) { Column( modifier = Modifier.offset { val scroll = scrollProvider() val offset = (maxOffset - scroll).coerceAtLeast(minOffset) IntOffset(x = 0, y = offset) } ) { … } }
△ Use a function that provides a scroll position instead of the original scroll position
At this point, we can just call this Lambda function at different times and read the scroll state. The offset modifier is used here, which accepts as a parameter a Lambda function that provides an offset value. This means that when the scroll changes, the modifier does not need to be recreated, the value of the scroll state is only read during the drop phase. So, we only need to perform drop and draw operations when the scroll state changes, no need to reorganize or measure, thus improving performance.
Going back to the bottom navigation example, it has the same problem and we can fix it in the same way:
@Composable fun BottomNavItem( icon: @Composable BoxScope.() -> Unit, text: @Composable BoxScope.() -> Unit, animationProgress: () -> Float ) { … val progress = animationProgress() val textWidth = textPlaceable.width * progress val iconX = (width - textWidth - iconPlaceable.width) / 2 val textX = iconX + iconPlaceable.width return layout(width, height) { iconPlaceable.placeRelative(iconX.toInt(), iconY) if (animationProgress != 0f) { textPlaceable.placeRelative(textX.toInt(), textY) } } }
△ Revised bottom navigation
We use a function that provides the current animation progress as a parameter, so no reorganization is required, only layout is performed.
There is one principle you need to grasp: whenever the parameters of composables or modifiers are likely to change frequently, caution should be exercised, as this can lead to overcomposition. Reorganization is only necessary when changing what is displayed, not when changing where or how it is displayed.
BoxWithConstraints can perform composition based on layout because it initiates subcomposition during the layout phase. For performance reasons, we want to avoid performing composition during layout as much as possible. So instead of BoxWithConstraints, we tend to use layouts that change based on size. BoxWithConstraints is only used when the message type changes with size.
Improve layout performance
Sometimes a layout knows its size without measuring all of its children. For example, there are cards with the following composition:
△ Example of layout card
The icon and title make up the title bar, and the rest is the text. The icon size is known to be a fixed value, and the title height is the same as the icon height. When measuring a card, you only need to measure the body, its constraint is the layout height minus 48 DP, and the height of the card is the height of the body plus 48 DP.
△ The measurement process only measures the size of the text
The system recognizes that only the body is measured, so it is the only significant child node that determines the size of the layout, icons and text still need to be measured, but can be done during placement.
△ Place process measurement icons and text
Assuming the title is "Layout", when the title changes, the system does not have to re-measure the layout, so the body will not be re-measured, saving unnecessary work.
△ No need to re-measure when the title changes
Summarize
In this article, we describe the implementation of custom layouts, and also use modifiers to build and incorporate layout behaviors, further reducing the difficulty of meeting exact functional needs. In addition, some advanced features of the layout system are introduced, such as custom alignment across nested hierarchies, creating custom ParentDataModifiers for own layouts, support for automatic right-to-left settings, and deferring composition operations until layout information is known time, wait. We also learned how to perform a single-place layout model, how to skip the remeasurement so that it only performs a reposition operation, and using these methods, you will be able to write high-performance layout logic that animates with gestures.
An understanding of the layout system can help you build layouts that meet your exact design needs, creating great apps that users love. To learn more, please review the resources listed below:
- Jetpack Compose Getting Started Documentation
- Jetpack Compose Learning Roadmap
- Jetpack Compose related examples
You are welcome to click here to submit feedback to us, or to share what you like and problems you find. Your feedback is very important to us, thank you for your support!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。