Welcome again to the MAD Expertise collection on Jetpack Compose layouts and modifiers! Within the earlier episode, we talked in regards to the Structure section of Compose to elucidate how modifier chaining order and incoming mum or dad constraints affect the composable they’re handed to.
In right this moment’s episode, we zoom in much more on the Structure section and constraints and canopy them from one other perspective — harness their energy to construct customized layouts in Compose.
To construct customized layouts, we’ll go over what the Structure section is able to, enter it and use its sub-phases — measurement and placement — to your benefit for constructing versatile, customized layouts.
After that, we’ll cowl two essential, rule-defying Compose APIs: SubcomposeLayout
, and Intrinsic measurements, because the final two lacking items of the Layouts puzzle. These ideas will give you further data to construct intricate designs with very particular necessities in Compose.
In case you’ve obtained any questions so removed from this collection on Compose Layouts and Modifiers, we could have a stay Q&A session on March ninth. Depart a remark right here, on YouTube, or utilizing #MADCompose on Twitter to ask your questions.
You may also watch this text as a MAD Expertise video:
Within the earlier episodes, we talked about how Compose transforms knowledge into UI through its three phases: Composition, Structure and Draw, or “what” to indicate, “the place” to position it, and “how” to render it.
However because the identify of our collection suggests, we’re principally within the Structure section.
Nonetheless, the time period “Structure” in Compose is used for, properly, quite a lot of various things and may appear complicated due to its many meanings. To date within the collection, we’ve realized the next usages:
- Structure section: considered one of three phases of Compose, through which a mum or dad structure defines the sizing and the positioning of its youngster parts
- A structure: a broad, summary time period used to rapidly outline any UI factor in Compose
- A structure node: an summary idea used as a visible illustration of 1 factor within the UI tree, created on account of the Composition section in Compose
On this episode, we’ll additionally get to be taught a number of further meanings to finish the entire Structure circle. Let’s rapidly break them down first — a extra in depth clarification of those phrases awaits additional down within the publish:
Structure
composable: the composable used because the core element of Compose UI. When referred to as throughout Composition, it creates and provides a structure node within the Compose UI tree; the idea for all increased degree layouts likeColumn
,Row
, and so on.structure()
perform — the start line of placement, which is the second sub-step of the Structure section and takes care of putting youngsters in aStructure
composable, proper after the primary sub-step of measurement.structure()
modifier — a modifier that wraps one single structure node and permits sizing and putting it individually, as an alternative of this being achieved by its mum or dad structure
Now that we all know what’s what, let’s begin with the Structure section and zoom in on it. As talked about, through the Structure section, every factor within the UI tree measures its youngsters, if any, and locations them within the accessible 2D house.
Each out-of-the-box structure in Compose, resembling Row
, Column
, and plenty of extra, deal with all of this for you robotically.
However what in case your designs require a non-standard structure, for which it’s worthwhile to go customized and construct your personal structure, like this TimeGraph
from our JetLagged pattern?
That’s exactly when it’s worthwhile to know extra in regards to the Structure section — enter it and use its sub-phases of kid measurement and placement to your benefit. So, let’s check out how we will construct a customized structure in Compose from a given design!
Let’s go over and clarify crucial, fundamental steps for constructing a customized structure. Nonetheless, when you want to observe a detailed, step-by-step video information on how and when to create a customized structure for an actual life, intricate app design, please check out the Customized layouts and graphics in Compose video or instantly discover the TimeGraph
customized structure from our JetLagged pattern.
Calling the Structure
composable is the start line of each the Structure section and constructing a customized structure:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Structure() { … }
}
The Structure
composable is the important protagonist of the Structure section in Compose and the core element of the Compose structure system:
@Composable inline enjoyable Structure(
content material: @Composable @UiComposable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
// …
}
It accepts a composable content material
as its youngsters, and a measure coverage for measuring and positioning its parts. All higher-level layouts, like Column
and Row
, use this composable below the hood.
Structure
composable presently has three overloads:
Structure
— for measuring and putting 0 or extra youngsters, which accepts one composable ascontent material
Structure
— for leaf nodes of the UI tree with exactly 0 youngsters, so it doesn’t have acontent material
parameterStructure
— accepts acontents
listing for passing a number of totally different composables
As soon as we enter the Structure section, we see that it consists of two steps, measurement and placement, in that particular order:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Structure(
content material = content material,
modifier = modifier,
measurePolicy = { measurables, constraints ->
// 1. Measurement step
// Decide sizes of elements
structure(…) {
// 2. Placement step
// Decide positions of elements
}
}
)
}
Sizes of kid parts are calculated through the measure cross, and their positions through the placement cross. The order of those steps is enforced with Kotlin DSL scopes that are nested in a manner that stops putting one thing that hasn’t first been measured or doing placement within the measurement scope:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Structure(
content material = content material,
modifier = modifier,
measurePolicy = { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
// Decide sizes of elements
structure(…) {
// PLACEMENT SCOPE
// 2. Placement step
// Decide positions of elements
}
}
)
}
Throughout measurement, the content material of the structure might be accessed as measurables
, or elements which can be able to be measured. Inside Structure, measurables
come as an inventory by default:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
// …
) {
Structure(
content material = content material,
modifier = modifier
measurePolicy = { measurables: Checklist<Measurable>, constraints: Constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
// Decide sizes of elements
}
)
}
Relying on the necessities of your customized structure, you can both take this listing and measure each merchandise with the identical incoming constraints, to maintain its authentic, predefined measurement:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
// …
) {
Structure(
content material = content material,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
measurables.map { measurable ->
measurable.measure(constraints)
}
}
}
Or tweak its measurements as wanted — by copying constraints you want to hold and overriding those you want to change:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
// …
) {
Structure(
content material = content material,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
measurables.map { measurable ->
measurable.measure(
constraints.copy(
minWidth = newWidth,
maxWidth = newWidth
)
)
}
}
}
We’ve seen within the earlier episode that constraints are handed down from mum or dad to youngster within the UI tree, through the Structure section. When a mum or dad node measures its youngsters, it supplies these constraints to every youngster to allow them to know what’s the minimal and the utmost measurement they’re allowed to be.
An important characteristic of the Structure section is the single cross measurement. Which means a structure factor could not measure any of its youngsters greater than as soon as. Single-pass measurement is sweet for efficiency, permitting Compose to effectively deal with deep UI bushes.
Measuring an inventory of measurables
would in return give an inventory of placeables
, or a element that’s now able to be positioned:
@Composable
enjoyable CustomLayout(
content material: @Composable () -> Unit,
// …
) {
Structure(
content material = content material,
modifier = modifier
) { measurables, constraints ->
// MEASUREMENT SCOPE
// 1. Measurement step
val placeables = measurables.map { measurable ->
// Returns a placeable
measurable.measure(constraints)
}
}
}
The location step begins by calling the structure()
perform and coming into the position scope. At this level, the mum or dad structure will have the ability to resolve by itself measurement (totalWidth
, totalHeight
), for instance, summing up the widths and heights of its youngster placeables:
@Composable
enjoyable CustomLayout(
// …
) {
Structure(
// …
) {
// totalWidth could possibly be the sum of all youngsters's widths
// totalHeight could possibly be the sum of all youngsters's heights
structure(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
}
}
}
Placement scope now permits us to make use of all placeables
that got here as a results of measurement:
@Composable
enjoyable CustomLayout(
// …
) {
Structure(
// …
) {
// …
structure(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
placeables // PLACE US! 😎
}
}
}
To start out putting youngsters, we’d like their beginning x and y coordinates. As soon as we outline the place we would like the kids to be positioned, we name place()
to conclude with the position cross:
@Composable
enjoyable CustomLayout(
// …
) {
Structure(
// …
) {
// …
structure(totalWidth, totalHeight) {
// PLACEMENT SCOPE
// 2. Placement step
placeables.map { it.place(xPosition, yPosition) }
}
}
}
And with that, we finish the position step, in addition to the Structure section! Your customized structure is now prepared for use and reused.
Utilizing the Structure composable to create a customized structure lets you manipulate all youngsters parts and manually management their sizing and positioning. Nonetheless, there are instances the place making a customized structure only for controlling one particular factor is an overkill and never crucial.
In these instances, relatively than utilizing customized layouts, Compose provides a greater and less complicated resolution — the .structure()
modifier, which lets you measure and lay out simply one, wrapped factor.
Let’s take a look at an instance the place a UI factor is being squashed by its mum or dad in a manner that we don’t actually like:
We wish only one Aspect
on this easy Column
to have extra width than the mum or dad is implementing by eradicating the encompassing 40.dp
padding
for it, for instance, to attain edge to edge look:
@Composable
enjoyable LayoutModifierExample() {
Column(
modifier = Modifier
.fillMaxWidth()
.background(Coloration.LightGray)
.padding(40.dp)
) {
Aspect()
Aspect()
// Merchandise under ought to insurgent towards the enforced padding and go edge to edge
Aspect()
Aspect()
}
}
To make the third factor management itself and take away the enforced padding, we set a .structure()
modifier on it.
The way in which it really works is similar to the Structure
composable. It accepts a lambda that grants you entry to the factor you’re measuring, handed as a single measurable
, and the composable’s incoming constraints from the mum or dad. You then use it to change how a single, wrapped factor is measured and laid out:
Modifier.structure { measurable, constraints ->
// Measurement
val placeable = measurable.measure(...)structure(placeable.width, placeable.top) {
// Placement
placeable.place(...)
}
}
Again to our instance — we then change this Aspect
’s most width within the measurement step so as to add an additional 80.dp
s:
Aspect(modifier = Modifier.structure { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(
// Resize this merchandise's maxWidth by including DPs to incoming constraints
maxWidth = constraints.maxWidth + 80.dp.roundToPx()
)
)
structure(placeable.width, placeable.top) {
// Place this merchandise within the authentic place
placeable.place(0, 0)
}
})
As we stated beforehand, one of many strengths of Compose is that you may select your personal path when downside fixing, as there are often a number of methods to attain the identical factor. In case you knew the precise, static measurement you need for this Aspect
, one other method could possibly be to set a .requiredWidth()
modifier on it, in order that the incoming constraints within the mum or dad structure don’t override its set width and as an alternative respect it. In distinction, use of standard .width()
modifier would have the set width
overridden by the mum or dad structure and incoming constraints within the measurement section.
In earlier episodes, we talked in regards to the phases of Compose and the rule of their exact ordering: 1. Composition, 2. Structure, and three. Drawing. The Structure section subsequently breaks all the way down to measurement and placement sub-phases. Whereas this is applicable to the overwhelming majority of Structure
composables, there’s one rule-breaking structure that doesn’t observe this schema, however with an excellent motive — SubcomposeLayout
.
Take into consideration the next use case — you’re constructing a listing of a thousand objects that merely can not match on the display all on the identical time. In that case, composing all these youngster objects could be an pointless waste of assets — why compose so many objects up entrance if the vast majority of them can not even be seen?
As an alternative, a greater method could be to 1. measure youngsters to acquire their sizes, then primarily based on that, 2. calculate what number of objects can match the accessible viewport and at last, compose solely objects that will be seen.
This is without doubt one of the important concepts behind SubcomposeLayout
— it must do the measurement cross first for some or all youngster composables after which secondly, to make use of that data to decide whether or not to compose some or all youngsters.
That is exactly why Lazy elements are constructed on high of SubcomposeLayout
, which permits them so as to add content material on demand whereas scrolling.
SubcomposeLayout
defers the Composition section till the Structure section, in order that the composition or execution of some youngster composables might be postponed till the mum or dad structure has extra data — for instance, the sizes of its youngster composables. That means, the measurement step within the Structure section must occur earlier than the Composition section.
BoxWithConstraints
additionally makes use of SubcomposeLayout
below the hood, however this use case is barely totally different — BoxWithConstraints
lets you receive the constraints handed by the mum or dad and use them within the deferred Composition section, as constraints are solely recognized within the Structure section measurement step:
BoxWithConstraints {
// maxHeight is the measurement data accessible solely in BoxWithConstraints,
// as a result of deferred Composition section occurring AFTER Structure section measurement
if (maxHeight < 300.dp) {
SmallImage()
} else {
BigImage()
}
}
SubcompositionLayout DON’Ts
As SubcompositionLayout
adjustments the standard stream of Compose phases to permit for dynamic execution, there are specific prices and limitations on the subject of efficiency. Subsequently, it’s crucial to know when SubcompositionLayout
needs to be used and when it’s not wanted.
An excellent, fast manner of realizing if you may want SubcomposeLayout
is when a minimum of one youngster composable’s Composition section is dependent upon the results of one other youngster composable’s measurement. We’ve seen legitimate use instances for this in Lazy elements and BoxWithConstraints
.
If nevertheless, you simply want one youngster’s measurement to measure different youngsters, you are able to do so with a daily Structure
composable. This fashion, you’ll be able to nonetheless measure objects individually, relying on one another’s consequence — you simply can not change their composition.
The second rule of Compose we beforehand talked about is the single cross measurement in Structure section, which helps drastically with general efficiency of this step and the structure system on the whole. Take into consideration the quantity of recompositions that may occur in a short while and the way limiting the measurement of your entire UI tree for every recomposition can enhance the general pace!
Nonetheless, there are use instances when the mum or dad structure must know some data about its youngsters earlier than measuring them, in order that it may use this data for defining and passing down constraints. And that is exactly what Intrinsic measurements do — they allow you to question youngsters earlier than they’re measured.
Let’s take a look at the next instance — we would like this Column
objects to have the identical width, or to be extra exact, have every merchandise’s width as that of the widest youngster (in our case, the “And Modifiers” merchandise). However, we additionally need that youngster to take as a lot width because it wants. So our first step is:
@Composable
enjoyable IntrinsicExample() {
Column() {
Textual content(textual content = "MAD")
Textual content(textual content = "Expertise")
Textual content(textual content = "Layouts")
Textual content(textual content = "And Modifiers")
}
}
Nonetheless, we will see that this isn’t sufficient. Every merchandise takes solely the house it requires. We will check out the next:
@Composable
enjoyable IntrinsicExample() {
Column() {
Textual content(textual content = "MAD", Modifier.fillMaxWidth())
Textual content(textual content = "Expertise", Modifier.fillMaxWidth())
Textual content(textual content = "Layouts", Modifier.fillMaxWidth())
Textual content(textual content = "And Modifiers", Modifier.fillMaxWidth())
}
}
Nonetheless, this expands each merchandise and the mum or dad Column
to the utmost width accessible on the display. Bear in mind, we would like the width of the widest merchandise for all objects. So, as you’ll be able to inform, we’re aiming to make use of Intrinsics right here:
@Composable
enjoyable IntrinsicExample() {
Column(Modifier.width(IntrinsicSize.Max)) {
Textual content(textual content = "MAD", Modifier.fillMaxWidth())
Textual content(textual content = "Expertise", Modifier.fillMaxWidth())
Textual content(textual content = "Layouts", Modifier.fillMaxWidth())
Textual content(textual content = "And Modifiers", Modifier.fillMaxWidth())
}
}
Through the use of IntrinsicSize.Max
on the mum or dad Column
, we’re querying its youngsters and asking “What’s the most width it’s worthwhile to show your whole content material correctly?”. Since we’re displaying textual content and the phrase “And Modifiers” is the longest, it can outline Column
’s width.
As soon as the intrinsic measurement is decided, it’s used to set the dimensions — on this case, the width — of the Column
and the remaining youngsters can then fill that width.
Conversely, if we use IntrinsicSize.Min
, the query will likely be “What’s the minimal width it’s worthwhile to show your whole content material correctly?” Within the case of textual content, the minimal intrinsic width is the one which has one phrase on every line:
@Composable
enjoyable IntrinsicExample() {
Column(Modifier.width(IntrinsicSize.Min) {
Textual content(textual content = "MAD", Modifier.fillMaxWidth())
Textual content(textual content = "Expertise", Modifier.fillMaxWidth())
Textual content(textual content = "Layouts", Modifier.fillMaxWidth())
Textual content(textual content = "And Modifiers", Modifier.fillMaxWidth())
}
}
To rapidly summarize the entire intrinsic mixtures accessible:
Modifier.width(IntrinsicSize.Min)
— “What’s the minimal width it’s worthwhile to show your content material correctly?”Modifier.width(IntrinsicSize.Max)
— “What’s the utmost width it’s worthwhile to show your content material correctly?”Modifier.top(IntrinsicSize.Min)
— “What’s the minimal top it’s worthwhile to show your content material correctly?”Modifier.top(IntrinsicSize.Max)
— “What’s the utmost top it’s worthwhile to show your content material correctly?”
Nonetheless, Intrinsic measurements don’t actually measure the kids twice. As an alternative, they do a distinct sort of calculation — you’ll be able to consider it as a pre-measure step with out requiring exponential measurement time, as it’s cheaper and simpler. So whereas this doesn’t precisely break the one measurement rule, it does bend it a little bit bit and reveals a Compose requirement that falls exterior of the standard ones.
When making a customized structure, Intrinsics present a default implementation primarily based on approximations. Nonetheless, in some instances, the default calculation may not be just right for you as supposed, so the API supplies a manner of overriding these defaults.
To specify the Intrinsic measurements of your customized structure, you’ll be able to override the minIntrinsicWidth
, minIntrinsicHeight
, maxIntrinsicWidth
, and maxIntrinsicHeight
of the MeasurePolicy
interface through the measurement cross:
Structure(
modifier = modifier,
content material = content material,
measurePolicy = object : MeasurePolicy {
override enjoyable MeasureScope.measure(
measurables: Checklist<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and structure right here
}override enjoyable IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: Checklist<IntrinsicMeasurable>,
width: Int
): Int {
// Logic for calculating customized maxIntrinsicHeight right here
}
// Different intrinsics associated strategies have a default worth,
// you'll be able to override solely the strategies that you just want.
}
)
We’ve got coated loads right this moment — all of the numerous meanings of the time period “Structure” and the way they relate to at least one one other, enter and management the Structure section to your benefit when constructing customized layouts, after which we concluded up with SubcompositionLayout
and Intrinsic measurements as the extra APIs for attaining very particular structure behaviors.
And with this, we conclude our MAD Expertise Compose Layouts and Modifiers collection! From the very fundamentals of Layouts and Modifiers, easy and highly effective supplied Compose layouts, Compose phases, to superior ideas resembling modifier chaining order and subcomposition in only a few episodes — congrats, you’ve come a great distance!
We hope you’ve realized new issues about Compose, renewed previous data and most significantly — that you just really feel way more ready and assured emigrate E V E R Y T H I N G to Compose 😀.
Bought any questions? Depart a remark under or use the #MADCompose hashtag on Twitter and we’ll deal with your questions in our upcoming stay Q&A for the collection on March ninth. Keep tuned!