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  * ![Row
70  * arrangements](https://developer.android.com/images/reference/androidx/compose/foundation/layout/row_arrangement_visualization.gif)
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