1 /*
<lambda>null2 * Copyright 2020 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.foundation.layout
18
19 import androidx.annotation.FloatRange
20 import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility
21 import androidx.compose.foundation.layout.internal.requirePrecondition
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.Immutable
24 import androidx.compose.runtime.Stable
25 import androidx.compose.runtime.remember
26 import androidx.compose.ui.Alignment
27 import androidx.compose.ui.Modifier
28 import androidx.compose.ui.layout.FirstBaseline
29 import androidx.compose.ui.layout.HorizontalAlignmentLine
30 import androidx.compose.ui.layout.IntrinsicMeasurable
31 import androidx.compose.ui.layout.IntrinsicMeasureScope
32 import androidx.compose.ui.layout.LastBaseline
33 import androidx.compose.ui.layout.Layout
34 import androidx.compose.ui.layout.Measurable
35 import androidx.compose.ui.layout.MeasurePolicy
36 import androidx.compose.ui.layout.MeasureResult
37 import androidx.compose.ui.layout.MeasureScope
38 import androidx.compose.ui.layout.Measured
39 import androidx.compose.ui.layout.Placeable
40 import androidx.compose.ui.unit.Constraints
41 import androidx.compose.ui.unit.LayoutDirection
42
43 /**
44 * A layout composable that places its children in a horizontal sequence. For a layout composable
45 * that places its children in a vertical sequence, see [Column]. Note that by default items do not
46 * scroll; see `Modifier.horizontalScroll` to add this behavior. For a horizontally scrollable list
47 * that only composes and lays out the currently visible items see `LazyRow`.
48 *
49 * The [Row] layout is able to assign children widths according to their weights provided using the
50 * [RowScope.weight] modifier. If a child is not provided a weight, it will be asked for its
51 * preferred width before the sizes of the children with weights are calculated proportionally to
52 * their weight based on the remaining available space. Note that if the [Row] is horizontally
53 * scrollable or part of a horizontally scrollable container, any provided weights will be
54 * disregarded as the remaining available space will be infinite.
55 *
56 * When none of its children have weights, a [Row] will be as small as possible to fit its children
57 * one next to the other. In order to change the width of the [Row], use the [Modifier.width]
58 * modifiers; e.g. to make it fill the available width [Modifier.fillMaxWidth] can be used. If at
59 * least one child of a [Row] has a [weight][RowScope.weight], the [Row] will fill the available
60 * width, so there is no need for [Modifier.fillMaxWidth]. However, if [Row]'s size should be
61 * limited, the [Modifier.width] or [Modifier.size] layout modifiers should be applied.
62 *
63 * When the size of the [Row] is larger than the sum of its children sizes, a
64 * [horizontalArrangement] can be specified to define the positioning of the children inside the
65 * [Row]. See [Arrangement] for available positioning behaviors; a custom arrangement can also be
66 * defined using the constructor of [Arrangement]. Below is an illustration of different horizontal
67 * arrangements:
68 *
69 * 
71 *
72 * Example usage:
73 *
74 * @sample androidx.compose.foundation.layout.samples.SimpleRow
75 *
76 * Note that if two or more Text components are placed in a [Row], normally they should be aligned
77 * by their first baselines. [Row] as a general purpose container does not do it automatically so
78 * developers need to handle this manually. This is achieved by adding a [RowScope.alignByBaseline]
79 * modifier to every such Text component. By default this modifier aligns by [FirstBaseline]. If,
80 * however, you need to align Texts by [LastBaseline] for example, use a more general
81 * [RowScope.alignBy] modifier.
82 *
83 * See example of using Texts inside the Row:
84 *
85 * @sample androidx.compose.foundation.layout.samples.SimpleAlignByInRow
86 * @param modifier The modifier to be applied to the Row.
87 * @param horizontalArrangement The horizontal arrangement of the layout's children.
88 * @param verticalAlignment The vertical alignment of the layout's children.
89 * @param content The content of the Row
90 * @see Column
91 * @see [androidx.compose.foundation.lazy.LazyRow]
92 */
93 @Composable
94 inline fun Row(
95 modifier: Modifier = Modifier,
96 horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
97 verticalAlignment: Alignment.Vertical = Alignment.Top,
98 content: @Composable RowScope.() -> Unit
99 ) {
100 val measurePolicy = rowMeasurePolicy(horizontalArrangement, verticalAlignment)
101 Layout(
102 content = { RowScopeInstance.content() },
103 measurePolicy = measurePolicy,
104 modifier = modifier
105 )
106 }
107
108 /** MeasureBlocks to use when horizontalArrangement and verticalAlignment are not provided. */
109 @PublishedApi
110 internal val DefaultRowMeasurePolicy: MeasurePolicy =
111 RowMeasurePolicy(
112 horizontalArrangement = Arrangement.Start,
113 verticalAlignment = Alignment.Top,
114 )
115
116 @PublishedApi
117 @Composable
rowMeasurePolicynull118 internal fun rowMeasurePolicy(
119 horizontalArrangement: Arrangement.Horizontal,
120 verticalAlignment: Alignment.Vertical
121 ): MeasurePolicy =
122 if (horizontalArrangement == Arrangement.Start && verticalAlignment == Alignment.Top) {
123 DefaultRowMeasurePolicy
124 } else {
<lambda>null125 remember(horizontalArrangement, verticalAlignment) {
126 RowMeasurePolicy(
127 horizontalArrangement = horizontalArrangement,
128 verticalAlignment = verticalAlignment,
129 )
130 }
131 }
132
133 internal data class RowMeasurePolicy(
134 private val horizontalArrangement: Arrangement.Horizontal,
135 private val verticalAlignment: Alignment.Vertical
136 ) : MeasurePolicy, RowColumnMeasurePolicy {
mainAxisSizenull137 override fun Placeable.mainAxisSize() = width
138
139 override fun Placeable.crossAxisSize() = height
140
141 override fun MeasureScope.measure(
142 measurables: List<Measurable>,
143 constraints: Constraints
144 ): MeasureResult {
145 return measure(
146 constraints.minWidth,
147 constraints.minHeight,
148 constraints.maxWidth,
149 constraints.maxHeight,
150 horizontalArrangement.spacing.roundToPx(),
151 this,
152 measurables,
153 arrayOfNulls(measurables.size),
154 0,
155 measurables.size
156 )
157 }
158
populateMainAxisPositionsnull159 override fun populateMainAxisPositions(
160 mainAxisLayoutSize: Int,
161 childrenMainAxisSize: IntArray,
162 mainAxisPositions: IntArray,
163 measureScope: MeasureScope
164 ) {
165 with(horizontalArrangement) {
166 measureScope.arrange(
167 mainAxisLayoutSize,
168 childrenMainAxisSize,
169 measureScope.layoutDirection,
170 mainAxisPositions
171 )
172 }
173 }
174
placeHelpernull175 override fun placeHelper(
176 placeables: Array<Placeable?>,
177 measureScope: MeasureScope,
178 beforeCrossAxisAlignmentLine: Int,
179 mainAxisPositions: IntArray,
180 mainAxisLayoutSize: Int,
181 crossAxisLayoutSize: Int,
182 crossAxisOffset: IntArray?,
183 currentLineIndex: Int,
184 startIndex: Int,
185 endIndex: Int
186 ): MeasureResult {
187 return with(measureScope) {
188 layout(mainAxisLayoutSize, crossAxisLayoutSize) {
189 placeables.forEachIndexed { i, placeable ->
190 val crossAxisPosition =
191 getCrossAxisPosition(
192 placeable!!,
193 placeable.rowColumnParentData,
194 crossAxisLayoutSize,
195 beforeCrossAxisAlignmentLine
196 )
197 placeable.place(mainAxisPositions[i], crossAxisPosition)
198 }
199 }
200 }
201 }
202
createConstraintsnull203 override fun createConstraints(
204 mainAxisMin: Int,
205 crossAxisMin: Int,
206 mainAxisMax: Int,
207 crossAxisMax: Int,
208 isPrioritizing: Boolean
209 ): Constraints {
210 return createRowConstraints(
211 isPrioritizing,
212 mainAxisMin,
213 crossAxisMin,
214 mainAxisMax,
215 crossAxisMax
216 )
217 }
218
getCrossAxisPositionnull219 private fun getCrossAxisPosition(
220 placeable: Placeable,
221 parentData: RowColumnParentData?,
222 crossAxisLayoutSize: Int,
223 beforeCrossAxisAlignmentLine: Int
224 ): Int {
225 val childCrossAlignment = parentData?.crossAxisAlignment
226 return childCrossAlignment?.align(
227 size = crossAxisLayoutSize - placeable.height,
228 layoutDirection = LayoutDirection.Ltr,
229 placeable = placeable,
230 beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
231 ) ?: verticalAlignment.align(0, crossAxisLayoutSize - placeable.height)
232 }
233
minIntrinsicWidthnull234 override fun IntrinsicMeasureScope.minIntrinsicWidth(
235 measurables: List<IntrinsicMeasurable>,
236 height: Int
237 ) =
238 IntrinsicMeasureBlocks.HorizontalMinWidth(
239 measurables,
240 height,
241 horizontalArrangement.spacing.roundToPx(),
242 )
243
244 override fun IntrinsicMeasureScope.minIntrinsicHeight(
245 measurables: List<IntrinsicMeasurable>,
246 width: Int
247 ) =
248 IntrinsicMeasureBlocks.HorizontalMinHeight(
249 measurables,
250 width,
251 horizontalArrangement.spacing.roundToPx(),
252 )
253
254 override fun IntrinsicMeasureScope.maxIntrinsicWidth(
255 measurables: List<IntrinsicMeasurable>,
256 height: Int
257 ) =
258 IntrinsicMeasureBlocks.HorizontalMaxWidth(
259 measurables,
260 height,
261 horizontalArrangement.spacing.roundToPx(),
262 )
263
264 override fun IntrinsicMeasureScope.maxIntrinsicHeight(
265 measurables: List<IntrinsicMeasurable>,
266 width: Int
267 ) =
268 IntrinsicMeasureBlocks.HorizontalMaxHeight(
269 measurables,
270 width,
271 horizontalArrangement.spacing.roundToPx(),
272 )
273 }
274
275 internal fun createRowConstraints(
276 isPrioritizing: Boolean,
277 mainAxisMin: Int,
278 crossAxisMin: Int,
279 mainAxisMax: Int,
280 crossAxisMax: Int
281 ): Constraints {
282 return if (!isPrioritizing) {
283 Constraints(
284 maxWidth = mainAxisMax,
285 maxHeight = crossAxisMax,
286 minWidth = mainAxisMin,
287 minHeight = crossAxisMin
288 )
289 } else {
290 Constraints.fitPrioritizingWidth(
291 maxWidth = mainAxisMax,
292 maxHeight = crossAxisMax,
293 minWidth = mainAxisMin,
294 minHeight = crossAxisMin
295 )
296 }
297 }
298
299 /** Scope for the children of [Row]. */
300 @LayoutScopeMarker
301 @Immutable
302 @JvmDefaultWithCompatibility
303 interface RowScope {
304 /**
305 * Size the element's width proportional to its [weight] relative to other weighted sibling
306 * elements in the [Row]. The parent will divide the horizontal space remaining after measuring
307 * unweighted child elements and distribute it according to this weight. When [fill] is true,
308 * the element will be forced to occupy the whole width allocated to it. Otherwise, the element
309 * is allowed to be smaller - this will result in [Row] being smaller, as the unused allocated
310 * width will not be redistributed to other siblings.
311 *
312 * @param weight The proportional width to give to this element, as related to the total of all
313 * weighted siblings. Must be positive.
314 * @param fill When `true`, the element will occupy the whole width allocated.
315 */
316 @Stable
weightnull317 fun Modifier.weight(
318 @FloatRange(from = 0.0, fromInclusive = false) weight: Float,
319 fill: Boolean = true
320 ): Modifier
321
322 /**
323 * Align the element vertically within the [Row]. This alignment will have priority over the
324 * [Row]'s `verticalAlignment` parameter.
325 *
326 * Example usage:
327 *
328 * @sample androidx.compose.foundation.layout.samples.SimpleAlignInRow
329 */
330 @Stable fun Modifier.align(alignment: Alignment.Vertical): Modifier
331
332 /**
333 * Position the element vertically such that its [alignmentLine] aligns with sibling elements
334 * also configured to `alignBy`. `alignBy` is a form of [align], so both modifiers will not work
335 * together if specified for the same layout. `alignBy` can be used to align two layouts by
336 * baseline inside a [Row], using `alignBy(FirstBaseline)`. Within a [Row], all components with
337 * `alignBy` will align vertically using the specified [HorizontalAlignmentLine]s or values
338 * provided using the other `alignBy` overload, forming a sibling group. At least one element of
339 * the sibling group will be placed as it had [Alignment.Top] align in [Row], and the alignment
340 * of the other siblings will be then determined such that the alignment lines coincide. Note
341 * that if only one element in a [Row] has the `alignBy` modifier specified the element will be
342 * positioned as if it had [Alignment.Top] align.
343 *
344 * Example usage:
345 *
346 * @sample androidx.compose.foundation.layout.samples.SimpleAlignByInRow
347 * @see alignByBaseline
348 */
349 @Stable fun Modifier.alignBy(alignmentLine: HorizontalAlignmentLine): Modifier
350
351 /**
352 * Position the element vertically such that its first baseline aligns with sibling elements
353 * also configured to `alignByBaseline` or `alignBy`. This modifier is a form of [align], so
354 * both modifiers will not work together if specified for the same layout. `alignByBaseline` is
355 * a particular case of `alignBy`. See `alignBy` for more details.
356 *
357 * Example usage:
358 *
359 * @sample androidx.compose.foundation.layout.samples.SimpleAlignByInRow
360 * @see alignBy
361 */
362 @Stable fun Modifier.alignByBaseline(): Modifier
363
364 /**
365 * Position the element vertically such that the alignment line for the content as determined by
366 * [alignmentLineBlock] aligns with sibling elements also configured to `alignBy`. `alignBy` is
367 * a form of [align], so both modifiers will not work together if specified for the same layout.
368 * Within a [Row], all components with `alignBy` will align vertically using the specified
369 * [HorizontalAlignmentLine]s or values obtained from [alignmentLineBlock], forming a sibling
370 * group. At least one element of the sibling group will be placed as it had [Alignment.Top]
371 * align in [Row], and the alignment of the other siblings will be then determined such that the
372 * alignment lines coincide. Note that if only one element in a [Row] has the `alignBy` modifier
373 * specified the element will be positioned as if it had [Alignment.Top] align.
374 *
375 * Example usage:
376 *
377 * @sample androidx.compose.foundation.layout.samples.SimpleAlignByInRow
378 */
379 @Stable fun Modifier.alignBy(alignmentLineBlock: (Measured) -> Int): Modifier
380 }
381
382 internal object RowScopeInstance : RowScope {
383 @Stable
384 override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
385 requirePrecondition(weight > 0.0) { "invalid weight; must be greater than zero" }
386 return this.then(
387 LayoutWeightElement(
388 // Coerce Float.POSITIVE_INFINITY to Float.MAX_VALUE to avoid errors
389 weight = weight.coerceAtMost(Float.MAX_VALUE),
390 fill = fill
391 )
392 )
393 }
394
395 @Stable
396 override fun Modifier.align(alignment: Alignment.Vertical) =
397 this.then(VerticalAlignElement(alignment))
398
399 @Stable
400 override fun Modifier.alignBy(alignmentLine: HorizontalAlignmentLine) =
401 this.then(WithAlignmentLineElement(alignmentLine = alignmentLine))
402
403 @Stable override fun Modifier.alignByBaseline() = alignBy(FirstBaseline)
404
405 override fun Modifier.alignBy(alignmentLineBlock: (Measured) -> Int) =
406 this.then(WithAlignmentLineBlockElement(block = alignmentLineBlock))
407 }
408