Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 1 | /* |
| 2 | * 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 | |
Louis Pullen-Freilich | 623e405 | 2020-07-19 20:24:03 +0100 | [diff] [blame] | 17 | package androidx.compose.foundation.layout |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 18 | |
Louis Pullen-Freilich | 1f10a59 | 2020-07-24 16:35:14 +0100 | [diff] [blame] | 19 | import androidx.compose.runtime.Composable |
| 20 | import androidx.compose.runtime.Immutable |
| 21 | import androidx.compose.runtime.Stable |
| 22 | import androidx.compose.runtime.remember |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 23 | import androidx.compose.ui.Alignment |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 24 | import androidx.compose.ui.Modifier |
Matvei Malkov | 103f605 | 2020-11-04 18:36:00 +0000 | [diff] [blame] | 25 | import androidx.compose.ui.layout.Layout |
Louis Pullen-Freilich | a03fd6c | 2020-07-24 23:26:29 +0100 | [diff] [blame] | 26 | import androidx.compose.ui.layout.Measured |
Matvei Malkov | 103f605 | 2020-11-04 18:36:00 +0000 | [diff] [blame] | 27 | import androidx.compose.ui.layout.VerticalAlignmentLine |
Jens Ole Lauridsen | 9945da7 | 2020-10-23 15:20:06 -0700 | [diff] [blame] | 28 | import androidx.compose.ui.platform.debugInspectorInfo |
Louis Pullen-Freilich | 1f2bcd8 | 2020-07-22 18:30:31 +0100 | [diff] [blame] | 29 | import androidx.compose.ui.util.annotation.FloatRange |
Nikolay Igotti | 63c6e12 | 2020-07-06 20:21:59 +0300 | [diff] [blame] | 30 | |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 31 | /** |
| 32 | * A layout composable that places its children in a vertical sequence. For a layout composable |
Mihai Popa | a842ce2 | 2020-11-18 14:41:59 +0000 | [diff] [blame] | 33 | * that places its children in a horizontal sequence, see [Row]. For a layout that places children |
| 34 | * in a vertical sequence and is also scrollable, see `ScrollableColumn`. For a vertically |
| 35 | * scrollable list that only composes and lays out the currently visible items see `LazyColumn`. |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 36 | * |
Mihai Popa | a842ce2 | 2020-11-18 14:41:59 +0000 | [diff] [blame] | 37 | * The [Column] layout is able to assign children heights according to their weights provided |
Mihai Popa | 5b7a6bb | 2020-04-08 20:11:03 +0100 | [diff] [blame] | 38 | * using the [ColumnScope.weight] modifier. If a child is not provided a weight, it will be |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 39 | * asked for its preferred height before the sizes of the children with weights are calculated |
| 40 | * proportionally to their weight based on the remaining available space. |
| 41 | * |
| 42 | * When none of its children have weights, a [Column] will be as small as possible to fit its |
Mihai Popa | ae5237e | 2020-05-19 19:15:42 +0100 | [diff] [blame] | 43 | * children one on top of the other. In order to change the height of the [Column], use the |
| 44 | * [Modifier.height] modifiers; e.g. to make it fill the available height [Modifier.fillMaxHeight] |
| 45 | * can be used. If at least one child of a [Column] has a [weight][ColumnScope.weight], |
| 46 | * the [Column] will fill the available height, so there is no need for [Modifier.fillMaxHeight]. |
| 47 | * However, if [Column]'s size should be limited, the [Modifier.height] or [Modifier.size] layout |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 48 | * modifiers should be applied. |
| 49 | * |
Mihai Popa | 09a9eb8 | 2020-04-08 23:16:46 +0100 | [diff] [blame] | 50 | * When the size of the [Column] is larger than the sum of its children sizes, a |
| 51 | * [verticalArrangement] can be specified to define the positioning of the children inside the |
| 52 | * [Column]. See [Arrangement] for available positioning behaviors; a custom arrangement can also |
| 53 | * be defined using the constructor of [Arrangement]. |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 54 | * |
| 55 | * Example usage: |
| 56 | * |
Louis Pullen-Freilich | 623e405 | 2020-07-19 20:24:03 +0100 | [diff] [blame] | 57 | * @sample androidx.compose.foundation.layout.samples.SimpleColumn |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 58 | * |
| 59 | * @param modifier The modifier to be applied to the Column. |
Mihai Popa | 09a9eb8 | 2020-04-08 23:16:46 +0100 | [diff] [blame] | 60 | * @param verticalArrangement The vertical arrangement of the layout's children. |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 61 | * @param horizontalAlignment The horizontal alignment of the layout's children. |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 62 | * |
Mihai Popa | a842ce2 | 2020-11-18 14:41:59 +0000 | [diff] [blame] | 63 | * @see Row |
| 64 | * @see [androidx.compose.foundation.ScrollableColumn] |
| 65 | * @see [androidx.compose.foundation.lazy.LazyColumn] |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 66 | */ |
| 67 | @Composable |
Chuck Jazdzewski | e933802 | 2020-07-22 11:31:22 -0700 | [diff] [blame] | 68 | inline fun Column( |
Adam Powell | b6d8db2 | 2020-04-02 12:40:03 -0700 | [diff] [blame] | 69 | modifier: Modifier = Modifier, |
Mihai Popa | 09a9eb8 | 2020-04-08 23:16:46 +0100 | [diff] [blame] | 70 | verticalArrangement: Arrangement.Vertical = Arrangement.Top, |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 71 | horizontalAlignment: Alignment.Horizontal = Alignment.Start, |
Louis Pullen-Freilich | dc68dd50 | 2020-11-13 02:10:48 +0000 | [diff] [blame] | 72 | content: @Composable ColumnScope.() -> Unit |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 73 | ) { |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 74 | val measureBlocks = columnMeasureBlocks(verticalArrangement, horizontalAlignment) |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 75 | Layout( |
Louis Pullen-Freilich | dc68dd50 | 2020-11-13 02:10:48 +0000 | [diff] [blame] | 76 | content = { ColumnScope.content() }, |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 77 | measureBlocks = measureBlocks, |
| 78 | modifier = modifier |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 79 | ) |
| 80 | } |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 81 | |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 82 | @PublishedApi |
| 83 | internal val DefaultColumnMeasureBlocks = rowColumnMeasureBlocks( |
| 84 | orientation = LayoutOrientation.Vertical, |
Mihai Popa | bc17985 | 2020-08-06 16:07:18 +0100 | [diff] [blame] | 85 | arrangement = { totalSize, size, _, density, outPosition -> |
| 86 | Arrangement.Top.arrange(totalSize, size, density, outPosition) |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 87 | }, |
Mihai Popa | bc17985 | 2020-08-06 16:07:18 +0100 | [diff] [blame] | 88 | arrangementSpacing = Arrangement.Top.spacing, |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 89 | crossAxisAlignment = CrossAxisAlignment.horizontal(Alignment.Start), |
| 90 | crossAxisSize = SizeMode.Wrap |
| 91 | ) |
| 92 | |
| 93 | @PublishedApi |
| 94 | @Composable |
| 95 | internal fun columnMeasureBlocks( |
| 96 | verticalArrangement: Arrangement.Vertical, |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 97 | horizontalAlignment: Alignment.Horizontal |
| 98 | ) = remember(verticalArrangement, horizontalAlignment) { |
| 99 | if (verticalArrangement == Arrangement.Top && horizontalAlignment == Alignment.Start) { |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 100 | DefaultColumnMeasureBlocks |
| 101 | } else { |
| 102 | rowColumnMeasureBlocks( |
| 103 | orientation = LayoutOrientation.Vertical, |
Mihai Popa | bc17985 | 2020-08-06 16:07:18 +0100 | [diff] [blame] | 104 | arrangement = { totalSize, size, _, density, outPosition -> |
| 105 | verticalArrangement.arrange(totalSize, size, density, outPosition) |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 106 | }, |
Mihai Popa | bc17985 | 2020-08-06 16:07:18 +0100 | [diff] [blame] | 107 | arrangementSpacing = verticalArrangement.spacing, |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 108 | crossAxisAlignment = CrossAxisAlignment.horizontal(horizontalAlignment), |
Chuck Jazdzewski | ef18c63 | 2020-06-30 08:29:29 -0700 | [diff] [blame] | 109 | crossAxisSize = SizeMode.Wrap |
| 110 | ) |
| 111 | } |
| 112 | } |
| 113 | |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 114 | /** |
| 115 | * Scope for the children of [Column]. |
| 116 | */ |
| 117 | @LayoutScopeMarker |
Leland Richardson | 47df77f | 2020-05-21 09:15:40 -0700 | [diff] [blame] | 118 | @Immutable |
Mihai Popa | c8deeac | 2020-09-07 12:16:36 +0100 | [diff] [blame] | 119 | interface ColumnScope { |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 120 | /** |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 121 | * Align the element horizontally within the [Column]. This alignment will have priority over |
| 122 | * the [Column]'s `horizontalAlignment` parameter. |
Mihai Popa | 294b06a | 2020-04-20 20:39:58 +0100 | [diff] [blame] | 123 | * |
| 124 | * Example usage: |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 125 | * @sample androidx.compose.foundation.layout.samples.SimpleAlignInColumn |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 126 | */ |
Leland Richardson | 47df77f | 2020-05-21 09:15:40 -0700 | [diff] [blame] | 127 | @Stable |
Jens Ole Lauridsen | 9945da7 | 2020-10-23 15:20:06 -0700 | [diff] [blame] | 128 | fun Modifier.align(alignment: Alignment.Horizontal) = this.then( |
| 129 | HorizontalAlignModifier( |
| 130 | horizontal = alignment, |
| 131 | inspectorInfo = debugInspectorInfo { |
| 132 | name = "align" |
| 133 | value = alignment |
| 134 | } |
| 135 | ) |
| 136 | ) |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 137 | |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 138 | /** |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 139 | * Position the element horizontally such that its [alignmentLine] aligns with sibling elements |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 140 | * also configured to [alignBy]. [alignBy] is a form of [align], |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 141 | * so both modifiers will not work together if specified for the same layout. |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 142 | * Within a [Column], all components with [alignBy] will align horizontally using |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 143 | * the specified [VerticalAlignmentLine]s or values provided using the other |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 144 | * [alignBy] overload, forming a sibling group. |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 145 | * At least one element of the sibling group will be placed as it had [Alignment.Start] align |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 146 | * in [Column], and the alignment of the other siblings will be then determined such that |
| 147 | * the alignment lines coincide. Note that if only one element in a [Column] has the |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 148 | * [alignBy] modifier specified the element will be positioned |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 149 | * as if it had [Alignment.Start] align. |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 150 | * |
| 151 | * Example usage: |
Louis Pullen-Freilich | 623e405 | 2020-07-19 20:24:03 +0100 | [diff] [blame] | 152 | * @sample androidx.compose.foundation.layout.samples.SimpleRelativeToSiblingsInColumn |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 153 | */ |
Leland Richardson | 47df77f | 2020-05-21 09:15:40 -0700 | [diff] [blame] | 154 | @Stable |
Jens Ole Lauridsen | 9945da7 | 2020-10-23 15:20:06 -0700 | [diff] [blame] | 155 | fun Modifier.alignBy(alignmentLine: VerticalAlignmentLine) = this.then( |
| 156 | SiblingsAlignedModifier.WithAlignmentLine( |
| 157 | line = alignmentLine, |
| 158 | inspectorInfo = debugInspectorInfo { |
| 159 | name = "alignBy" |
| 160 | value = alignmentLine |
| 161 | } |
| 162 | ) |
| 163 | ) |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 164 | |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 165 | @Deprecated( |
| 166 | "alignWithSiblings was renamed to alignBy.", |
| 167 | ReplaceWith("alignBy(alignmentLine)") |
| 168 | ) |
| 169 | fun Modifier.alignWithSiblings(alignmentLine: VerticalAlignmentLine) = alignBy(alignmentLine) |
| 170 | |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 171 | /** |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 172 | * Size the element's height proportional to its [weight] relative to other weighted sibling |
| 173 | * elements in the [Column]. The parent will divide the vertical space remaining after measuring |
| 174 | * unweighted child elements and distribute it according to this weight. |
Mihai Popa | c4391e8 | 2020-05-15 13:54:37 +0000 | [diff] [blame] | 175 | * When [fill] is true, the element will be forced to occupy the whole height allocated to it. |
| 176 | * Otherwise, the element is allowed to be smaller - this will result in [Column] being smaller, |
| 177 | * as the unused allocated height will not be redistributed to other siblings. |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 178 | * |
Louis Pullen-Freilich | 623e405 | 2020-07-19 20:24:03 +0100 | [diff] [blame] | 179 | * @sample androidx.compose.foundation.layout.samples.SimpleColumn |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 180 | */ |
Leland Richardson | 47df77f | 2020-05-21 09:15:40 -0700 | [diff] [blame] | 181 | @Stable |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 182 | fun Modifier.weight( |
Nikolay Igotti | 63c6e12 | 2020-07-06 20:21:59 +0300 | [diff] [blame] | 183 | @FloatRange(from = 0.0, to = 3.4e38 /* POSITIVE_INFINITY */, fromInclusive = false) |
| 184 | weight: Float, |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 185 | fill: Boolean = true |
| 186 | ): Modifier { |
| 187 | require(weight > 0.0) { "invalid weight $weight; must be greater than zero" } |
Jens Ole Lauridsen | b4dadfe | 2020-10-27 13:51:34 -0700 | [diff] [blame] | 188 | return this.then( |
| 189 | LayoutWeightImpl( |
| 190 | weight = weight, |
| 191 | fill = fill, |
| 192 | inspectorInfo = debugInspectorInfo { |
| 193 | name = "weight" |
| 194 | value = weight |
| 195 | properties["weight"] = weight |
| 196 | properties["fill"] = fill |
| 197 | } |
| 198 | ) |
| 199 | ) |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | /** |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 203 | * Position the element horizontally such that the alignment line for the content as |
| 204 | * determined by [alignmentLineBlock] aligns with sibling elements also configured to |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 205 | * [alignBy]. [alignBy] is a form of [align], so both modifiers |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 206 | * will not work together if specified for the same layout. |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 207 | * Within a [Column], all components with [alignBy] will align horizontally using |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 208 | * the specified [VerticalAlignmentLine]s or values obtained from [alignmentLineBlock], |
| 209 | * forming a sibling group. |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 210 | * At least one element of the sibling group will be placed as it had [Alignment.Start] align |
Mihai Popa | 3f98a4b | 2020-04-20 16:39:49 +0100 | [diff] [blame] | 211 | * in [Column], and the alignment of the other siblings will be then determined such that |
| 212 | * the alignment lines coincide. Note that if only one element in a [Column] has the |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 213 | * [alignBy] modifier specified the element will be positioned |
Mihai Popa | d0e8f05 | 2020-09-02 15:49:54 +0100 | [diff] [blame] | 214 | * as if it had [Alignment.Start] align. |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 215 | * |
| 216 | * Example usage: |
Louis Pullen-Freilich | 623e405 | 2020-07-19 20:24:03 +0100 | [diff] [blame] | 217 | * @sample androidx.compose.foundation.layout.samples.SimpleRelativeToSiblings |
Adam Powell | 999a89b | 2020-03-11 09:08:07 -0700 | [diff] [blame] | 218 | */ |
Leland Richardson | 47df77f | 2020-05-21 09:15:40 -0700 | [diff] [blame] | 219 | @Stable |
Jens Ole Lauridsen | 9945da7 | 2020-10-23 15:20:06 -0700 | [diff] [blame] | 220 | fun Modifier.alignBy(alignmentLineBlock: (Measured) -> Int) = this.then( |
| 221 | SiblingsAlignedModifier.WithAlignmentLineBlock( |
| 222 | block = alignmentLineBlock, |
| 223 | inspectorInfo = debugInspectorInfo { |
| 224 | name = "alignBy" |
| 225 | value = alignmentLineBlock |
| 226 | } |
| 227 | ) |
| 228 | ) |
Mihai Popa | c8deeac | 2020-09-07 12:16:36 +0100 | [diff] [blame] | 229 | |
Mihai Popa | 0f1851a8 | 2020-10-12 16:01:55 +0100 | [diff] [blame] | 230 | @Deprecated( |
| 231 | "alignWithSiblings was renamed to alignBy.", |
| 232 | ReplaceWith("alignBy(alignmentLineBlock)") |
| 233 | ) |
| 234 | fun Modifier.alignWithSiblings(alignmentLineBlock: (Measured) -> Int) = |
| 235 | alignBy(alignmentLineBlock) |
| 236 | |
Mihai Popa | c8deeac | 2020-09-07 12:16:36 +0100 | [diff] [blame] | 237 | companion object : ColumnScope |
Mihai Popa | 53db327 | 2020-03-16 17:06:47 +0000 | [diff] [blame] | 238 | } |