1 /*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.ui.layout
18
19 import androidx.compose.runtime.Stable
20 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
21 import androidx.compose.ui.unit.Constraints
22 import androidx.compose.ui.util.fastMap
23
24 /**
25 * Defines the measure and layout behavior of a [Layout]. [Layout] and [MeasurePolicy] are the way
26 * Compose layouts (such as `Box`, `Column`, etc.) are built, and they can also be used to achieve
27 * custom layouts.
28 *
29 * See [Layout] samples for examples of how to use [MeasurePolicy].
30 *
31 * Intrinsic measurement methods define the intrinsic size of the layout. These can be queried by
32 * the layout's parent in order to obtain, in specific cases, more information about the size of the
33 * layout in the absence of specific constraints:
34 * - [minIntrinsicWidth] defines the minimum width this layout can take, given a specific height,
35 * such that the content of the layout will be painted correctly
36 * - [minIntrinsicHeight] defines the minimum height this layout can take, given a specific width,
37 * such that the content of the layout will be painted correctly
38 * - [maxIntrinsicWidth] defines the minimum width such that increasing it further will not decrease
39 * the minimum intrinsic height
40 * - [maxIntrinsicHeight] defines the minimum height such that increasing it further will not
41 * decrease the minimum intrinsic width
42 *
43 * Most layout scenarios do not require querying intrinsic measurements. Therefore, when writing a
44 * custom layout, it is common to only define the actual measurement, as most of the times the
45 * intrinsic measurements of the layout will not be queried. Moreover, intrinsic measurement methods
46 * have default implementations that make a best effort attempt to calculate the intrinsic
47 * measurements by reusing the [measure] method. Note this will not be correct for all layouts, but
48 * can be a convenient approximation.
49 *
50 * Intrinsic measurements can be useful when the layout system enforcement of no more than one
51 * measurement per child is limiting. Layouts that use them are the `preferredWidth(IntrinsicSize)`
52 * and `preferredHeight(IntrinsicSize)` modifiers. See their samples for when they can be useful.
53 *
54 * @see Layout
55 */
56 @Stable
57 @JvmDefaultWithCompatibility
interfacenull58 fun interface MeasurePolicy {
59 /**
60 * The function that defines the measurement and layout. Each [Measurable] in the [measurables]
61 * list corresponds to a layout child of the layout, and children can be measured using the
62 * [Measurable.measure] method. This method takes the [Constraints] which the child should
63 * respect; different children can be measured with different constraints.
64 *
65 * Measuring a child returns a [Placeable], which reveals the size chosen by the child as a
66 * result of its own measurement. According to the children sizes, the parent defines the
67 * position of the children, by [placing][Placeable.PlacementScope.place] the [Placeable]s in
68 * the [MeasureResult.placeChildren] of the returned [MeasureResult]. Therefore the parent needs
69 * to measure its children with appropriate [Constraints], such that whatever valid sizes
70 * children choose, they can be laid out correctly according to the parent's layout algorithm.
71 * This is because there is no measurement negotiation between the parent and children: once a
72 * child chooses its size, the parent needs to handle it correctly.
73 *
74 * Note that a child is allowed to choose a size that does not satisfy its constraints. However,
75 * when this happens, the placeable's [width][Placeable.width] and [height][Placeable.height]
76 * will not represent the real size of the child, but rather the size coerced in the child's
77 * constraints. Therefore, it is common for parents to assume in their layout algorithm that its
78 * children will always respect the constraints. When this does not happen in reality, the
79 * position assigned to the child will be automatically offset to be centered on the space
80 * assigned by the parent under the assumption that constraints were respected. Rarely, when a
81 * parent really needs to know the true size of the child, they can read this from the
82 * placeable's [Placeable.measuredWidth] and [Placeable.measuredHeight].
83 *
84 * [MeasureResult] objects are usually created using the [MeasureScope.layout] factory, which
85 * takes the calculated size of this layout, its alignment lines, and a block defining the
86 * positioning of the children layouts.
87 */
88 fun MeasureScope.measure(measurables: List<Measurable>, constraints: Constraints): MeasureResult
89
90 /**
91 * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. It represents the
92 * minimum width this layout can take, given a specific height, such that the content of the
93 * layout can be painted correctly. There should be no side-effect from implementers of
94 * [minIntrinsicWidth].
95 */
96 fun IntrinsicMeasureScope.minIntrinsicWidth(
97 measurables: List<IntrinsicMeasurable>,
98 height: Int
99 ): Int {
100 val mapped =
101 measurables.fastMap {
102 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Width)
103 }
104 val constraints = Constraints(maxHeight = height)
105 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
106 val layoutResult = layoutReceiver.measure(mapped, constraints)
107 return layoutResult.width
108 }
109
110 /**
111 * The function used to calculate [IntrinsicMeasurable.minIntrinsicHeight]. It represents the
112 * minimum height this layout can take, given a specific width, such that the content of the
113 * layout will be painted correctly. There should be no side-effect from implementers of
114 * [minIntrinsicHeight].
115 */
116 fun IntrinsicMeasureScope.minIntrinsicHeight(
117 measurables: List<IntrinsicMeasurable>,
118 width: Int
119 ): Int {
120 val mapped =
121 measurables.fastMap {
122 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Height)
123 }
124 val constraints = Constraints(maxWidth = width)
125 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
126 val layoutResult = layoutReceiver.measure(mapped, constraints)
127 return layoutResult.height
128 }
129
130 /**
131 * The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth]. It represents the
132 * minimum width such that increasing it further will not decrease the minimum intrinsic height.
133 * There should be no side-effects from implementers of [maxIntrinsicWidth].
134 */
135 fun IntrinsicMeasureScope.maxIntrinsicWidth(
136 measurables: List<IntrinsicMeasurable>,
137 height: Int
138 ): Int {
139 val mapped =
140 measurables.fastMap {
141 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Width)
142 }
143 val constraints = Constraints(maxHeight = height)
144 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
145 val layoutResult = layoutReceiver.measure(mapped, constraints)
146 return layoutResult.width
147 }
148
149 /**
150 * The function used to calculate [IntrinsicMeasurable.maxIntrinsicHeight]. It represents the
151 * minimum height such that increasing it further will not decrease the minimum intrinsic width.
152 * There should be no side-effects from implementers of [maxIntrinsicHeight].
153 */
154 fun IntrinsicMeasureScope.maxIntrinsicHeight(
155 measurables: List<IntrinsicMeasurable>,
156 width: Int
157 ): Int {
158 val mapped =
159 measurables.fastMap {
160 DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Height)
161 }
162 val constraints = Constraints(maxWidth = width)
163 val layoutReceiver = IntrinsicsMeasureScope(this, layoutDirection)
164 val layoutResult = layoutReceiver.measure(mapped, constraints)
165 return layoutResult.height
166 }
167 }
168