skill: add Android skills
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
To configure the behavior of the `FlexBox` container, create a `FlexBoxConfig`
|
||||
block and supply it using the `config` parameter.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox(
|
||||
config = {
|
||||
direction(FlexDirection.Column)
|
||||
wrap(FlexWrap.Wrap)
|
||||
alignItems(FlexAlignItems.Center)
|
||||
alignContent(FlexAlignContent.SpaceAround)
|
||||
justifyContent(FlexJustifyContent.Center)
|
||||
gap(16.dp)
|
||||
}
|
||||
) { // child items
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
Use `FlexBoxConfig` to define the layout direction, wrapping behavior,
|
||||
alignment, and gaps between items.
|
||||
|
||||
## Layout direction
|
||||
|
||||
The `direction` function sets the main axis, which dictates the direction
|
||||
items are laid out in. It accepts the following values:
|
||||
|
||||
- `Row` (default): Sets the main axis to be horizontal. In left-to-right locales this will be left-to-right, with the opposite in right-to-left.
|
||||
- `RowReverse`: Reverses the direction of `Row`.
|
||||
- `Column`: Sets the main axis to be vertical, top-to-bottom.
|
||||
- `ColumnReverse`: Reverses the direction of `Column`.
|
||||
|
||||
## Align items and distribute extra space
|
||||
|
||||
The following sections describe how to align items and distribute extra space
|
||||
along the main and cross axes.
|
||||
|
||||
### Along the main axis
|
||||
|
||||
Use `justifyContent` to distribute items along the main axis. The following
|
||||
table shows the behavior when the direction is `Row`.
|
||||
|
||||
|---|---|
|
||||
| |  |
|
||||
| `Start` |  |
|
||||
| `Center` |  |
|
||||
| `End` |  |
|
||||
| `SpaceBetween` |  |
|
||||
| `SpaceAround` |  |
|
||||
| `SpaceEvenly` |  |
|
||||
|
||||
### Along the cross axis
|
||||
|
||||
Use `alignItems` to align items along the cross axis within a single line. This
|
||||
behavior can be overridden by individual items using the
|
||||
[`alignSelf` modifier](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-alignment).
|
||||
|
||||
The following images show the behavior when the direction is `Row`:
|
||||
|
||||
|---|---|---|---|---|---|
|
||||
|  |  |  |  |  |  |
|
||||
| | `Start` | `End` | `Center` | `Stretch` | `Baseline` |
|
||||
|
||||
Use `alignContent` to align lines to the cross axis and to distribute extra
|
||||
space between lines. This property only applies when there are multiple lines
|
||||
(wrapping is enabled). The following images show the behavior when the direction
|
||||
is `Row`:
|
||||
|
||||
|---|---|---|---|---|---|---|
|
||||
|  |  |  |  |  |  |  |
|
||||
| | `Start` | `End` | `Center` | `Stretch` | `SpaceBetween` | `SpaceAround` |
|
||||
|
||||
## Wrap items
|
||||
|
||||
Wrapping lets a `FlexBox` container become multi-line, moving items that don't
|
||||
fit onto a new row or column along the cross-axis. Configure wrapping behavior
|
||||
using `wrap`.
|
||||
|
||||
|---|---|
|
||||
| **`FlexWrap` value** | **Example using direction `Row`** |
|
||||
| `NoWrap` (default): Prevents items from wrapping. Items overflow if the main size is insufficient. |  |
|
||||
| `Wrap`: When there is insufficient space for an item (plus any [gap](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#add-gaps)), a new line is created in the direction of the cross axis. For example, if the direction is `Row`, a new line is added **below**. |  |
|
||||
| `WrapReverse`: The same as `Wrap`, except the new line is added in the opposite direction to the cross axis. For example, if the direction is `Row`, a new line is added **above**. |  |
|
||||
|
||||
The following example shows how the `FlexBox` wrapping algorithm works. The
|
||||
`FlexBox` container has a main size of `100dp`, with `wrap` set to
|
||||
`FlexWrap.Wrap` and a gap of `8dp`. It contains three items with `basis` `20dp`,
|
||||
`40dp`, and `50dp`, respectively.
|
||||
|
||||
There is `100dp` available space in the line. Child 1 is `20dp`.
|
||||
There is space, so Child 1 is placed into the line.
|
||||
 **Figure 1.** First item placed in the `FlexBox` container.
|
||||
|
||||
There is `80dp` available space in the line. The gap is `8dp`. Child 2 is
|
||||
`40dp`. The required space is `48dp`. There is space, so the gap and Child 2
|
||||
are placed into the line.
|
||||
 **Figure 2.** Second item placed in the `FlexBox` container after the first item.
|
||||
|
||||
There is `32dp` available space in the line. The gap is `8dp`. Child 3 is
|
||||
`50dp`. The required space is `58dp`. There is not enough space in the current
|
||||
line, so Child 3 is placed in a new line.
|
||||
 **Figure 3.** Third item placed on a new line because it doesn't fit on the first line.
|
||||
|
||||
## Add gaps between items
|
||||
|
||||
Add gaps between rows and columns using `rowGap` and `columnGap`. This is useful
|
||||
to avoid adding spacing modifiers to children.
|
||||
|
||||
|---|---|---|
|
||||
|  |  |  |
|
||||
| `rowGap` adds vertical space between items and lines. | `columnGap` adds horizontal space between items and lines. | `gap` is a convenience function that adds both `columnGap` and `rowGap`. |
|
||||
@@ -0,0 +1,69 @@
|
||||
This page describes how to implement basic `FlexBox` layouts.
|
||||
|
||||
## Set up project
|
||||
|
||||
1. Add the [`androidx.compose.foundation.layout`](https://developer.android.com/jetpack/androidx/versions) library to your project's
|
||||
`lib.versions.toml`.
|
||||
|
||||
[versions]
|
||||
compose = "1.12.0-beta02"
|
||||
|
||||
[libraries]
|
||||
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" }
|
||||
|
||||
2. Add the library dependency to your app's `build.gradle.kts`.
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
}
|
||||
|
||||
## Create basic FlexBox layouts
|
||||
|
||||
**Example 1** : `FlexBox` lays out two `Text` elements that are centrally
|
||||
aligned.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox(
|
||||
config = {
|
||||
direction(FlexDirection.Column)
|
||||
alignItems(FlexAlignItems.Center)
|
||||
}
|
||||
) {
|
||||
Text(text = "Hello", fontSize = 48.sp)
|
||||
Text(text = "World!", fontSize = 48.sp)
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||

|
||||
|
||||
**Example 2** : `FlexBox` wraps five items onto two rows and grows them unequally
|
||||
to fill the available space on each row. There is an `8.dp`
|
||||
gap, both vertically and horizontally, between the items.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox(
|
||||
config = {
|
||||
wrap(FlexWrap.Wrap)
|
||||
gap(8.dp)
|
||||
}
|
||||
) {
|
||||
// All boxes have an intrinsic width of 100.dp
|
||||
// Some grow to fill any remaining space on the row.
|
||||
RedRoundedBox()
|
||||
BlueRoundedBox()
|
||||
GreenRoundedBox(modifier = Modifier.flex { grow(1.0f) })
|
||||
OrangeRoundedBox(modifier = Modifier.flex { grow(1.0f) })
|
||||
PinkRoundedBox(modifier = Modifier.flex { grow(1.0f) })
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||

|
||||
|
||||
To learn more about `FlexBox` behavior, see [Set container behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior) and [Set
|
||||
item behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior).
|
||||
@@ -0,0 +1,81 @@
|
||||
> [!NOTE]
|
||||
> **Note:** FlexBox is an experimental API and is likely to change in the future. To use it, annotate your code with `@ExperimentalFlexBoxApi`. Please file any issues or feedback on the [issue tracker](https://issuetracker.google.com/issues/new?component=1876021&title=%5BFlexBox%5D).
|
||||
|
||||
[`FlexBox`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/FlexBox.composable#FlexBox(androidx.compose.ui.Modifier,androidx.compose.foundation.layout.FlexBoxConfig,kotlin.Function1)) is a container that lays out items in a single direction. It can
|
||||
resize, wrap, align, and distribute space among items to optimally fill the
|
||||
available space. It's a useful layout for different sized items and for resizing
|
||||
items when the available space changes.
|
||||
|
||||
With `FlexBox`, you can:
|
||||
|
||||
- Control how items grow and shrink to fill the available space
|
||||
- Wrap items onto new rows or columns when there isn't enough space for them
|
||||
- Distribute extra space between items using convenient presets
|
||||
|
||||
## When to use FlexBox
|
||||
|
||||
`FlexBox` is usually used to display a small number of items *within* an
|
||||
overall screen layout. For an overall screen layout,
|
||||
`Grid` is usually a better choice. `FlexBox` does not support lazy-loading of
|
||||
items. To display large numbers of items, use [lazy lists and grids](https://developer.android.com/develop/ui/compose/lists). If you
|
||||
need to wrap items, use `FlexBox` instead of `FlowRow` and `FlowColumn`.
|
||||
|
||||
## Terminology and concepts
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Key Point:** `FlexBox` is heavily influenced by the [CSS Flexible Box Layout specification](https://www.w3.org/TR/css-flexbox-1/) and has almost identical concepts, terminology, and behavior. If you're familiar with `display: flex`, you'll find `FlexBox`'s properties and behavior almost identical.
|
||||
|
||||
`FlexBox` lays out its items in either horizontal or vertical *lines* . This
|
||||
direction of these lines establishes the *main axis* . 90 degrees to the main
|
||||
axis is the *cross axis* . The length of the `FlexBox` along the main axis is
|
||||
known as the *main size* . The corresponding cross axis length is known as the
|
||||
*cross size* . These sizes and axes form the basis of `FlexBox`'s behavior.
|
||||
|
||||
|
||||
 **Figure 1.** Axes and sizes when the `FlexBox` direction is `Row`.  **Figure 2.** Axes and sizes when the `FlexBox` direction is `Column`.
|
||||
|
||||
<br />
|
||||
|
||||
### Apply properties
|
||||
|
||||
You can apply `FlexBox` properties in two ways:
|
||||
|
||||
- To the `FlexBox` container using `FlexBox(config)`
|
||||
- To an item inside the `FlexBox` using `Modifier.flex`
|
||||
|
||||
| **Container properties (`config`**) | **Item properties (`Modifier.flex`**) |
|
||||
|---|---|
|
||||
| - `direction` - the item layout direction - `wrap` - whether to wrap items if the **main size** is insufficient - `justifyContent` - how to **distribute** items along the **main axis** - `alignItems` - how to **align** items along the **cross axis** - `alignContent` - how to distribute extra space from the **cross size** when there are multiple lines - `rowGap` / `columnGap` - adds space between items and lines See [Set container behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior) for more information about these properties. | - `basis` - the size of the item before any extra space from the **main size** is distributed - `grow` - the share of extra space from the **main size** that this item should receive - `shrink` - the share of space deficit from the **main size** that this item should receive - `alignSelf` - how to distribute extra space from the **cross size** to this item, overrides `alignItems` - `order` - controls the layout order See [Set item behavior](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior) for more information about these properties. |
|
||||
|
||||
### Understand the `FlexBox` layout algorithm
|
||||
|
||||
One of `FlexBox`'s most powerful features is its ability to resize its children
|
||||
to best fit the space available to it. Understanding how `FlexBox` does this can
|
||||
help you set `FlexBox` properties to optimize your UI for all possible sizes.
|
||||
|
||||
`FlexBox`'s layout algorithm works in the following way:
|
||||
|
||||
1. **Calculate child base size** : Use the child's [`basis` value](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#set-initial-size)
|
||||
to calculate its initial size along the main axis before any extra space is
|
||||
distributed.
|
||||
|
||||
2. **Sort the children** : Sort the children by their [`order`](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-order) values, if
|
||||
present.
|
||||
|
||||
3. **Build lines** : For each child, check if its initial size plus
|
||||
[`gap`](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#add-gaps) will fit into the remaining space on the current line.
|
||||
If so, place this child into the line. If not, place it onto a new line if
|
||||
[wrapping is enabled](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#wrap-items), or place the item into the current line
|
||||
where it will overflow (it will be partially obscured by the edge of the
|
||||
container).
|
||||
|
||||
4. **Align or resize items in the main axis** : For each line, distribute extra
|
||||
space *to* or between items by [resizing](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/item-behavior#item-size) or
|
||||
[aligning](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#main-axis) them.
|
||||
|
||||
5. **Align or resize items in the cross axis** : For each line, distribute extra
|
||||
space to or between items and lines by [stretching or aligning
|
||||
them](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#cross-axis).
|
||||
|
||||
Now that you're familiar with `FlexBox` concepts, see [Get started](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/get-started) to
|
||||
create a basic `FlexBox`.
|
||||
@@ -0,0 +1,170 @@
|
||||
Use `Modifier.flex` to control how an item changes size, order, and is aligned
|
||||
inside a `FlexBox`.
|
||||
|
||||
## Item size
|
||||
|
||||
Use the `basis`, `grow`, and `shrink` functions to control an item's size.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox {
|
||||
RedRoundedBox(
|
||||
modifier = Modifier.flex {
|
||||
basis(FlexBasis.Auto)
|
||||
grow(1.0f)
|
||||
shrink(0.5f)
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Set initial size
|
||||
|
||||
Use `basis` to specify the item's initial size before any extra space is
|
||||
distributed. You can think of this as the item's *preferred* size.
|
||||
|
||||
|---|---|---|---|
|
||||
| **Value type** | **Behavior** | **Code snippet** Note: The boxes have a maximum intrinsic size of `100dp` | **Example using container width `600dp`** |
|
||||
| `Auto` (default) | Use the item's maximum intrinsic size. For example, a `Text` composable's maximum intrinsic width is the width of all its text on a single line - no wrapping. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(FlexBasis.Auto) } ) BlueRoundedBox( Modifier.flex { basis(FlexBasis.Auto) } ) } ``` |  |
|
||||
| Fixed `dp` | A fixed size in Dp. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(200.dp) } ) BlueRoundedBox( Modifier.flex { basis(100.dp) } ) } ``` |  |
|
||||
| Percentage | A percentage of the container size. | ```kotlin FlexBox { RedRoundedBox( Modifier.flex { basis(0.7f) } ) BlueRoundedBox( Modifier.flex { basis(0.3f) } ) } ``` |  |
|
||||
|
||||
If the basis value is less than the item's intrinsic minimum size, the intrinsic
|
||||
minimum size is used instead. For example, if a `Text` item that contains a word
|
||||
requires `50dp` to display, but also has `basis = 10.dp`, a
|
||||
value of `50dp` is used.
|
||||
|
||||
### Grow items when there's space
|
||||
|
||||
Use `grow` to specify how much an item grows when there is extra space. This is
|
||||
space remaining in the `FlexBox` container after all the items' `basis` values
|
||||
have been added up. The `grow` value indicates *how much* of the extra space a
|
||||
given child will receive, relative to its siblings. By default, items won't
|
||||
grow.
|
||||
|
||||
The following example shows a `FlexBox` with three child items. Each has a basis
|
||||
value of `100dp`. The first child has a positive `grow` value. Since there is
|
||||
only one child with a `grow` value, the actual value is irrelevant - as long as
|
||||
it's positive, the child receives all the extra space.
|
||||
|
||||
The images show the `FlexBox` behavior when its container size is `600dp`.
|
||||
|
||||
|---|---|
|
||||
| ```kotlin FlexBox { RedRoundedBox( title = "400dp", modifier = Modifier.flex { grow(1f) } ) BlueRoundedBox(title = "100dp") GreenRoundedBox(title = "100dp") } ``` | Each child has a basis value of `100dp`. There is `300dp` of extra space.  Child 1 grows by `300dp` to fill the extra space.  |
|
||||
|
||||
In the following example, the container size and `basis` size are the same. The
|
||||
difference is that each child has a different `grow` value.
|
||||
|
||||
|---|---|
|
||||
| ```kotlin FlexBox { RedRoundedBox( title = "150dp", modifier = Modifier.flex { grow(1f) } ) BlueRoundedBox( title = "200dp", modifier = Modifier.flex { grow(2f) } ) GreenRoundedBox( title = "250dp", modifier = Modifier.flex { grow(3f) } ) } ``` | Each child has a basis value of `100dp`. There is `300dp` of extra space.  The total grow value is 6. Child 1 grows by (1 / 6) \* 300 = `50dp` Child 2 grows by (2 / 6) \* 300 = `100dp` Child 3 grows by (3 / 6) \* 300 = `150dp`  |
|
||||
|
||||
### Shrink items when there's insufficient space
|
||||
|
||||
Use `shrink` to specify how much an item shrinks when the `FlexBox` container
|
||||
has insufficient space for all the items. `shrink` works the same way as `grow`
|
||||
except that, instead of distributing *extra space* to items, the *space deficit*
|
||||
is distributed to items. The `shrink` value specifies how much of the space
|
||||
deficit the item receives, or rather, how much the item will shrink by. By
|
||||
default, items have a `shrink` value of `1f`, meaning they shrink equally.
|
||||
|
||||
The following example shows two `Text` composables with the same text. The first
|
||||
child has a shrink value of `1f`, meaning it shrinks to absorb all the space
|
||||
deficit.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox {
|
||||
Text(
|
||||
"The quick brown fox",
|
||||
fontSize = 36.sp,
|
||||
modifier = Modifier
|
||||
.background(PastelRed)
|
||||
.flex { shrink(1f) }
|
||||
)
|
||||
Text(
|
||||
"The quick brown fox",
|
||||
fontSize = 36.sp,
|
||||
modifier = Modifier
|
||||
.background(PastelBlue)
|
||||
.flex { shrink(0f) }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
As the container size shrinks, Child 1 shrinks.
|
||||
|
||||
|---|---|
|
||||
| **Container size** | **FlexBox UI** |
|
||||
| `700dp` |  |
|
||||
| `500dp` |  |
|
||||
| `450dp` |  |
|
||||
|
||||
## Item alignment
|
||||
|
||||
Use `alignSelf` to control how an item is aligned to the cross axis. This
|
||||
overrides the [`alignItems` property](https://developer.android.com/develop/ui/compose/layouts/adaptive/flexbox/container-behavior#align-distribute) of the container for this item. It
|
||||
has all the same possible values, with the addition of `Auto` which inherits the
|
||||
behavior of the `FlexBox` container.
|
||||
|
||||
For example, this `FlexBox` has `alignItems` set to `Start` and five children
|
||||
which override the cross axis alignment.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox(
|
||||
config = {
|
||||
alignItems(FlexAlignItems.Start)
|
||||
}
|
||||
) {
|
||||
RedRoundedBox()
|
||||
BlueRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Center) })
|
||||
GreenRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.End) })
|
||||
PinkRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Stretch) })
|
||||
OrangeRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Baseline) })
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||

|
||||
|
||||
## Item order
|
||||
|
||||
By default, `FlexBox` lays out items in the order that they are declared in
|
||||
code. Override this behavior using `order`.
|
||||
|
||||
The default value for `order` is zero, and `FlexBox` sorts items based on this
|
||||
value in ascending order. Any items that have the same `order` value are
|
||||
laid out in the same order they are declared in. Use negative and positive
|
||||
`order` values to move items to the start or end of a layout without changing
|
||||
where they are declared.
|
||||
|
||||
The following example shows two child items. The first has the default `order`
|
||||
of zero, and the second has an order of `-1`. After sorting, Child 1 appears
|
||||
after Child 2.
|
||||
|
||||
|
||||
```kotlin
|
||||
FlexBox {
|
||||
// Declared first, but will be placed after visually
|
||||
RedRoundedBox(
|
||||
title = "World"
|
||||
)
|
||||
|
||||
// Declared second, but will be placed first visually
|
||||
BlueRoundedBox(
|
||||
title = "Hello",
|
||||
modifier = Modifier.flex {
|
||||
order(-1)
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||

|
||||
@@ -0,0 +1,320 @@
|
||||
You can define a Grid container configuration to create flexible layouts
|
||||
that respond to different screen sizes and content types.
|
||||
This page describes how to do the following:
|
||||
|
||||
- [Define a grid](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-definition): Set up the basic structure of rows and columns.
|
||||
- [Place items in a grid](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#item-placement): Understand how items are placed into grid cells and how to change flow direction.
|
||||
- [Manage track sizing](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-track-size): Use fixed, percentage, flexible, and intrinsic sizing to set track sizes.
|
||||
- [Set gaps](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-gap): Manage the "gutters" between rows and columns.
|
||||
|
||||
## Define a grid
|
||||
|
||||
A grid consists of columns and rows.
|
||||
The [`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) composable has a `config` parameter
|
||||
that accepts a lambda to define the columns and rows
|
||||
within [`GridConfigurationScope`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope).
|
||||
The following example defines a grid that has three rows and two columns,
|
||||
each with a fixed size specified in [`Dp`](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp):
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
}
|
||||
) {
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
## Place items in a grid
|
||||
|
||||
`Grid` takes the UI elements
|
||||
in the `content` lambda and places them into grid cells.
|
||||
The grid lays out items regardless of
|
||||
whether you have explicitly defined the rows and columns.
|
||||
By default,
|
||||
`Grid` tries to place a UI element in the available grid cell in the row;
|
||||
if it can't, it places it in an available grid cell in the next row.
|
||||
If there are no empty cells, `Grid` creates a new row.
|
||||
|
||||
In the following example, the grid has six grid cells
|
||||
and places a card into each one (Figure 1).
|
||||
Each grid cell is `160dp` x `90dp`,
|
||||
making the total grid size `320dp` x `270dp`.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Card1()
|
||||
Card2()
|
||||
Card3()
|
||||
Card4()
|
||||
Card5()
|
||||
Card6()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 1**. Six cards are placed in a grid that has three rows and two columns.
|
||||
|
||||
To change this default behavior to filling by column,
|
||||
set the [`flow`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#flow()) property to [`GridFlow.Column`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridFlow#Column()).
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
gap(8.dp)
|
||||
flow = GridFlow.Column // Grid tries to place items to fill the column
|
||||
},
|
||||
) {
|
||||
Card1()
|
||||
Card2()
|
||||
Card3()
|
||||
Card4()
|
||||
Card5()
|
||||
Card6()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 2** . `GridFlow.Row` (left) and `GridFlow.Column` (right).
|
||||
|
||||
## Manage track sizing
|
||||
|
||||
Rows and columns are collectively referred to as a [grid track](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-track).
|
||||
You can specify the size of a grid track using one of the following methods:
|
||||
|
||||
- **Fixed** (`Dp`): Allocates a specific size (e.g., `column(180.dp)`).
|
||||
- **Percentage** (`Float`): Allocates a percentage of the total available space from `0.0f` to `1.0f` (e.g., `row(0.5f)` for 50%).
|
||||
- **Flexible** ([`Fr`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Fr)): Distributes remaining space proportionally after fixed and percentage tracks are calculated. For example, if two rows are set to `1.fr` and `3.fr`, the latter receives 75% of the remaining height.
|
||||
- **Intrinsic** : Sizes the track based on the content inside it. For more information, see [Determine grid track size intrinsically](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#intrinsic-grid-track-size).
|
||||
|
||||
The following example uses the different track sizing options
|
||||
to define the row heights:
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
column(1f)
|
||||
|
||||
row(100.dp)
|
||||
row(0.2f)
|
||||
row(1.fr)
|
||||
row(GridTrackSize.Auto)
|
||||
},
|
||||
modifier = Modifier.height(480.dp)
|
||||
) {
|
||||
PastelRedCard("Fixed(100.dp)")
|
||||
PastelGreenCard("Percentage(0.2f)")
|
||||
PastelBlueCard("Flex(1.fr)")
|
||||
PastelYellowCard("Auto")
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 3** . Row heights defined using the four primary track sizing options in `Grid`.
|
||||
|
||||
### Set the minimum size for flexible grid tracks
|
||||
|
||||
When a grid container has no remaining space,
|
||||
a standard flexible track can shrink to `0.dp`.
|
||||
To prevent this and ensure content isn't crushed,
|
||||
use [`GridTrackSize.MinMax`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MinMax(androidx.compose.ui.unit.Dp,androidx.compose.foundation.layout.Fr))
|
||||
to enforce an explicit minimum size while keeping the track flexible.
|
||||
|
||||
The following example allocates at least `100.dp` to the first row:
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
column(1f)
|
||||
// The first row has a minimum height of 100.dp and can expand to
|
||||
// the half of the remaining space.
|
||||
row(GridTrackSize.MinMax(100.dp, 1.fr))
|
||||
// The second row takes the half of the remaining space.
|
||||
row(1.fr)
|
||||
// The third row has a fixed height of 200.dp.
|
||||
row(200.dp)
|
||||
},
|
||||
modifier = Modifier.size(360.dp) // Total grid height is 360.dp
|
||||
) {
|
||||
PastelRedCard("MinMax(100.dp, 1.fr)")
|
||||
PastelGreenCard("Flex(1.fr)")
|
||||
PastelBlueCard("Fixed(200.dp)")
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 4** . The first row has at least `100.dp` height.
|
||||
|
||||
### Set the minimum grid track size to place lazy lists
|
||||
|
||||
Standard flexible tracks automatically query the intrinsic sizes of
|
||||
their children to establish a base size.
|
||||
However, Jetpack Compose prohibits querying the intrinsic sizes of
|
||||
[`SubcomposeLayout`](https://developer.android.com/reference/kotlin/androidx/compose/ui/layout/SubcomposeLayout.composable#SubcomposeLayout(androidx.compose.ui.Modifier,kotlin.Function2)), which backs components,
|
||||
such as [`LazyColumn`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyColumn.composable) and [`LazyRow`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyRow.composable).
|
||||
|
||||
Placing a lazy list inside a standard flexible track causes
|
||||
an [`IllegalStateException`](https://developer.android.com/reference/java/lang/IllegalStateException) crash.
|
||||
To safely place lazy lists inside a flexible grid track,
|
||||
use `MinMax` with an explicit minimum size (such as `0.dp`)
|
||||
to bypass the intrinsic measurement pass.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
column(1f)
|
||||
// The first row's height is determined by the height of the Text composable.
|
||||
row(GridTrackSize.Auto)
|
||||
// The second row occupies the remaining space, allowing the LazyColumn to scroll.
|
||||
row(GridTrackSize.MinMax(0.dp, 1.fr))
|
||||
|
||||
gap(8.dp)
|
||||
},
|
||||
modifier = Modifier.size(width = 170.dp, height = 240.dp)
|
||||
) {
|
||||
Text("Lazy column in a Grid")
|
||||
// The LazyColumn is placed in the second row, filling the remaining space.
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
items(100) { number ->
|
||||
PastelGreenCard("Card $number")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 5** . `LazyColumn` in a grid cell.
|
||||
|
||||
### Determine grid track size intrinsically
|
||||
|
||||
You can use [intrinsic sizing](https://developer.android.com/develop/ui/compose/layouts/intrinsic-measurements) for a `Grid`
|
||||
when you want the layout to adapt to the content,
|
||||
rather than forcing it into a fixed container.
|
||||
The grid track size is determined with the following values:
|
||||
|
||||
- [`GridTrackSize.MaxContent`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MaxContent()): Use the content's maximum intrinsic size (e.g., the width is determined by the full length of the text in a text block with no wrapping).
|
||||
- [`GridTrackSize.MinContent`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#MinContent()): Use the content's minimum intrinsic size (e.g., the width is determined by the longest single word in a text block).
|
||||
- [`GridTrackSize.Auto`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridTrackSize#Auto()): Use a flexible size for a track that adapts based on available space. It behaves like `MaxContent` by default, but shrinks and wraps its content to fit within the parent container.
|
||||
|
||||
The following example places two texts side by side.
|
||||
The column size for the first text is determined
|
||||
by the required minimum width to display the text,
|
||||
and the second column width depends on the required maximum width of the text.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
column(GridTrackSize.MinContent)
|
||||
column(GridTrackSize.MaxContent)
|
||||
row(1.0f)
|
||||
},
|
||||
modifier = Modifier.width(480.dp)
|
||||
) {
|
||||
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras imperdiet.")
|
||||
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras imperdiet.")
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 5**. Intrinsic sizes specified in the columns.
|
||||
|
||||
## Set gaps between rows and columns
|
||||
|
||||
Once your grid tracks are sized,
|
||||
you can modify the [grid gap](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-gap) to refine the spacing between the tracks.
|
||||
You can specify the column gap with the [`columnGap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#columnGap(androidx.compose.ui.unit.Dp)) function,
|
||||
and the row gap with [`rowGap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#rowGap(androidx.compose.ui.unit.Dp)). In the following example,
|
||||
there is a `16dp` gap between each row,
|
||||
and an `8dp` gap between each column (Figure 5).
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
rowGap(16.dp)
|
||||
columnGap(8.dp)
|
||||
}
|
||||
) {
|
||||
Card1()
|
||||
Card2()
|
||||
Card3()
|
||||
Card4()
|
||||
Card5()
|
||||
Card6()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 6**. Gaps between rows and columns.
|
||||
|
||||
You can also use the convenience function [`gap`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridConfigurationScope#gap(androidx.compose.ui.unit.Dp))
|
||||
to define gaps of the same column and row size,
|
||||
and to define column and gap sizes separately using a single function.
|
||||
The following code adds `8dp` gaps to the grid:
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
gap(8.dp) // Equivalent to columnGap(8.dp) and rowGap(8.dp)
|
||||
}
|
||||
) {
|
||||
Card1()
|
||||
Card2()
|
||||
Card3()
|
||||
Card4()
|
||||
Card5()
|
||||
Card6()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
@@ -0,0 +1,51 @@
|
||||
This page describes how to implement basic [`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) layouts.
|
||||
|
||||
## Set up project
|
||||
|
||||
1. Add the [`androidx.compose.foundation.layout`](https://developer.android.com/jetpack/androidx/versions) library to your project's
|
||||
`lib.versions.toml`.
|
||||
|
||||
[versions]
|
||||
compose = "1.12.0-beta02"
|
||||
|
||||
[libraries]
|
||||
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" }
|
||||
|
||||
2. Add the library dependency to your app's `build.gradle.kts`.
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
}
|
||||
|
||||
## Create a basic grid
|
||||
|
||||
The following example creates a basic 2x3 grid,
|
||||
with the columns and rows having a fixed size of `100.dp`.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(100.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(100.dp)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Card1(containerColor = PastelRed)
|
||||
Card2(containerColor = PastelGreen)
|
||||
Card3(containerColor = PastelBlue)
|
||||
Card4(containerColor = PastelPink)
|
||||
Card5(containerColor = PastelOrange)
|
||||
Card6(containerColor = PastelYellow)
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 1**. A basic grid consists of rows and columns with fixed size.
|
||||
|
||||
To learn how to implement more advanced grids,
|
||||
see [Set container properties](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties) and [Set item properties](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/item-properties).
|
||||
@@ -0,0 +1,73 @@
|
||||
> [!NOTE]
|
||||
> **Note:** `Grid` is an experimental API and is subject to change. File any issues on the [issue tracker](https://issuetracker.google.com/issues/new?component=1876021&template=1424126).
|
||||
|
||||
[`Grid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/Grid.composable#Grid(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)) is a Jetpack Compose API
|
||||
that lets you flexibly implement a two-dimensional layout.
|
||||
With this API, you can display items in multi-column
|
||||
or multi-row layouts that adapt to the available container size.
|
||||
 **Figure 1.** A flexible and adaptive two-dimensional layout with `Grid`.
|
||||
|
||||
## How is Grid different from similar composables?
|
||||
|
||||
Compose already offers similar components, such as [`LazyVerticalGrid`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/grid/LazyVerticalGrid.composable#LazyVerticalGrid(androidx.compose.foundation.lazy.grid.GridCells,androidx.compose.ui.Modifier,androidx.compose.foundation.lazy.grid.LazyGridState,androidx.compose.foundation.layout.PaddingValues,kotlin.Boolean,androidx.compose.foundation.layout.Arrangement.Vertical,androidx.compose.foundation.layout.Arrangement.Horizontal,androidx.compose.foundation.gestures.FlingBehavior,kotlin.Boolean,androidx.compose.foundation.OverscrollEffect,kotlin.Function1)).
|
||||
These components are mainly for visualization of large, homogeneous data sets---
|
||||
for example, displaying a content catalog in a video streaming app.
|
||||
These components are NOT designed
|
||||
for the structural layout of a screen or complex component.
|
||||
|
||||
You can also implement a two-dimensional layout
|
||||
by combining multiple `Row` and `Column` composables.
|
||||
However, this approach has some downsides,
|
||||
such as deep hierarchies and difficulties in adaptability.
|
||||
|
||||
The following table provides an overview
|
||||
of which layouts are suitable for each API:
|
||||
|
||||
| Component | Purpose |
|
||||
|---|---|
|
||||
| `LazyVerticalGrid`, `LazyStaggeredGrid`, `LazyHorizontalGrid` | Visualization of large, homogeneous data sets that require lazy loading. |
|
||||
| `Row`, `Column`, `FlexBox` | One-dimensional layout |
|
||||
| `Grid` | Two-dimensional layout |
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** `Grid` doesn't support lazy loading.
|
||||
|
||||
## Terminology
|
||||
|
||||
Familiarize yourself with the following terminology
|
||||
to understand how `Grid` works.
|
||||
|
||||
### Grid line
|
||||
|
||||
A grid is made up of lines, which run horizontally and vertically.
|
||||
If your grid has three rows, it has four horizontal lines,
|
||||
including the one after the last row.
|
||||
In the following image, each dotted line represents a grid line:
|
||||
 **Figure 2**. The grid consists of four horizontal lines and three vertical lines.
|
||||
|
||||
### Grid track
|
||||
|
||||
A grid track is the space between two grid lines.
|
||||
A row track is between two horizontal lines,
|
||||
and a column track is between two vertical lines.
|
||||
To define the size of these tracks,
|
||||
assign a size to them when you create the grid.
|
||||
 **Figure 3**. A grid track for the first row.
|
||||
|
||||
### Grid cell
|
||||
|
||||
A grid cell is the intersection of a row and column track.
|
||||
 **Figure 4**. A grid cell that is an intersection of the second row and the second column.
|
||||
|
||||
### Grid area
|
||||
|
||||
A grid area consists of several grid cells.
|
||||
You can define a grid area by making an item span multiple tracks.
|
||||
 **Figure 5**. A grid area that consists of four grid cells.
|
||||
|
||||
### Grid gap
|
||||
|
||||
A grid gap is the gutter between grid tracks.
|
||||
You can't place a UI element into a gap,
|
||||
but you can span a UI element across it.
|
||||
 **Figure 6**. A grid gap between the first column and the second column.
|
||||
@@ -0,0 +1,168 @@
|
||||
While the `Grid` config defines the overall structure,
|
||||
you use the [`gridItem`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridScope#(androidx.compose.ui.Modifier).gridItem(kotlin.Int,kotlin.Int,kotlin.Int,kotlin.Int,androidx.compose.ui.Alignment)) modifier to control the position, spanning,
|
||||
and alignment of items within that structure.
|
||||
|
||||
## Set the item position
|
||||
|
||||
Place an item into a specific track or cell
|
||||
with the `row` and `column` parameters.
|
||||
|
||||
The `row` and `column` parameters specify the row and column track indexes
|
||||
that the item is placed in.
|
||||
Track indexes are 1-based---they start at one.
|
||||
Specifying only `row` or `column` (not both) places the item
|
||||
in the next available space in that track.
|
||||
Specifying both places the item into that cell.
|
||||
|
||||
Use a positive integer to specify the track index from the start.
|
||||
For example, to place an item in the first row and column,
|
||||
use `gridItem(row = 1, column = 1)`.
|
||||
|
||||
Use a negative integer to specify the track relative to the end.
|
||||
For example, to place an item in the second-to-last row and column, use
|
||||
`gridItem(row = -2, column = -2)`.
|
||||
|
||||
In the following example, Card **#2** is placed
|
||||
in the second row and the second column.
|
||||
Card **#3** is assigned to the last row (indexed by -1),
|
||||
where it automatically occupies
|
||||
the first available column in that track (Figure 1).
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
gap(8.dp)
|
||||
}
|
||||
) {
|
||||
Card1()
|
||||
Card2(modifier = Modifier.gridItem(row = 2, column = 2))
|
||||
Card3(modifier = Modifier.gridItem(row = -1, column = -2))
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 1** . Card **#2** is placed in the grid cell in the second row and the second column, and Card **#3** is placed in the first column in the third row.
|
||||
|
||||
## Span rows and columns
|
||||
|
||||
Use the `rowSpan` and `columnSpan` parameters
|
||||
to span an item over multiple cells.
|
||||
You can place a UI element into a [grid area](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-area),
|
||||
which is the area consisting of several [grid cells](https://developer.android.com/develop/ui/compose/layouts/adaptive/grid#grid-cell).
|
||||
The `gridItem` modifier lets you specify the grid area
|
||||
with the `rowSpan` and `columnSpan` parameters.
|
||||
In the following example,
|
||||
Card **#1** is placed in the area consisting of two rows and two columns
|
||||
(Figure 2).
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(3) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
rowGap(8.dp)
|
||||
columnGap(8.dp)
|
||||
}
|
||||
) {
|
||||
Card1(modifier = Modifier.gridItem(rowSpan = 2, columnSpan = 2))
|
||||
Card2()
|
||||
Card3()
|
||||
Card4(modifier = Modifier.gridItem(columnSpan = 3))
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 2** . Card **#4** spans three columns.
|
||||
|
||||
## Set the alignment in a grid area
|
||||
|
||||
You can set the alignment of the UI element in a grid area
|
||||
by specifying it in the `alignment` parameter of the [`gridItem`](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/GridScope#(androidx.compose.ui.Modifier).gridItem(kotlin.Int,kotlin.Int,kotlin.Int,kotlin.Int,androidx.compose.ui.Alignment)) modifier.
|
||||
In the following example, **#1** is placed in the center of the grid area
|
||||
consisting of two columns and two rows.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(3) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
rowGap(8.dp)
|
||||
columnGap(8.dp)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = "#1",
|
||||
modifier = Modifier
|
||||
.gridItem(
|
||||
rowSpan = 2,
|
||||
columnSpan = 2,
|
||||
alignment = Alignment.Center
|
||||
),
|
||||
)
|
||||
Card2()
|
||||
Card3()
|
||||
Card4(modifier = Modifier.gridItem(columnSpan = 3))
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 3** . The Text with **#1** is placed in the center of the grid area consisting of two rows and two columns.
|
||||
|
||||
## Auto-placement mixed with placed items
|
||||
|
||||
A UI element in `Grid`
|
||||
that has no position specification undergoes auto-placement.
|
||||
This example shows how you can mix auto-placed elements
|
||||
and the UI elements with specified grid cells.
|
||||
Card **#2** and Card **#4** are placed in specified grid cells,
|
||||
and the other items are auto-placed.
|
||||
|
||||
|
||||
```kotlin
|
||||
Grid(
|
||||
config = {
|
||||
repeat(2) {
|
||||
column(160.dp)
|
||||
}
|
||||
repeat(3) {
|
||||
row(90.dp)
|
||||
}
|
||||
rowGap(16.dp)
|
||||
columnGap(8.dp)
|
||||
}
|
||||
) {
|
||||
Card1()
|
||||
Card2(modifier = Modifier.gridItem(row = 2, column = 2))
|
||||
Card3()
|
||||
Card4(modifier = Modifier.gridItem(row = 3, column = 1))
|
||||
Card5()
|
||||
Card6()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
 **Figure 4** . Card **#3** is placed next to Card **#1**, as it is an auto-placement.
|
||||
@@ -0,0 +1,329 @@
|
||||
> [!NOTE]
|
||||
> **Note:** The `mediaQuery` function and the related data types are experimental and subject to change. File any issues on the [issue tracker](https://issuetracker.google.com/issues?q=componentid:1876021).
|
||||
|
||||
You need various types of information, such as device capability
|
||||
and app status, to update your app layout.
|
||||
Window width and height are the most commonly used information.
|
||||
In addition to that, you can refer to the following information:
|
||||
|
||||
- Window posture
|
||||
- Pointing devices precision
|
||||
- Keyboard type
|
||||
- Whether the camera and microphone are supported by the device
|
||||
- The distance between a user and the device display
|
||||
|
||||
Because the information is updated dynamically,
|
||||
you need to monitor it and trigger recomposition when any update happens.
|
||||
The [`mediaQuery`](https://developer.android.com/reference/kotlin/androidx/compose/ui/mediaQuery.composable#mediaQuery(kotlin.Function1)) function abstracts the details of the information retrieval
|
||||
and lets you focus on defining the condition to trigger the layout updates.
|
||||
The following example switches the layout to `TabletopLayout`
|
||||
when the foldable posture is tabletop:
|
||||
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun VideoPlayer(
|
||||
// ...
|
||||
) {
|
||||
// ...
|
||||
if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
|
||||
TabletopLayout()
|
||||
} else {
|
||||
FlatLayout()
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
## Enable the `mediaQuery` function
|
||||
|
||||
To enable the `mediaQuery` function,
|
||||
set the `isMediaQueryIntegrationEnabled` attribute of
|
||||
the [`ComposeUiFlags`](https://developer.android.com/reference/kotlin/androidx/compose/ui/ComposeUiFlags) object to `true`:
|
||||
|
||||
|
||||
```kotlin
|
||||
class MyApplication : Application() {
|
||||
override fun onCreate() {
|
||||
ComposeUiFlags.isMediaQueryIntegrationEnabled = true
|
||||
super.onCreate()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
## Define a condition with parameters
|
||||
|
||||
You can define a condition as a lambda
|
||||
that is evaluated within [`UiMediaScope`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope).
|
||||
The `mediaQuery` function evaluates the condition according to
|
||||
the current status and the device capabilities.
|
||||
The function returns a boolean value,
|
||||
so you can determine the layout with conditional branches
|
||||
like an `if` expression.
|
||||
Table 1 describes the parameters available in `UiMediaScope`.
|
||||
|
||||
| Parameter | Value type | Description |
|
||||
|---|---|---|
|
||||
| `windowWidth` | [`Dp`](https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp) | The current window width in dp. |
|
||||
| `windowHeight` | `Dp` | The current window height in dp. |
|
||||
| `windowPosture` | [`UiMediaScope.Posture`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.Posture) | The current posture of the application window. |
|
||||
| `pointerPrecision` | [`UiMediaScope.PointerPrecision`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision) | The highest precision of the available pointing devices. |
|
||||
| `keyboardKind` | [`UiMediaScope.KeyboardKind`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind) | The type of keyboard available or connected. |
|
||||
| `hasCamera` | `Boolean` | Whether the camera is supported on the device. |
|
||||
| `hasMicrophone` | `Boolean` | Whether the microphone is supported on the device. |
|
||||
| `viewingDistance` | [`UiMediaScope.ViewingDistance`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance) | The typical distance between the user and the device screen. |
|
||||
|
||||
A `UiMediaScope` object resolves the values of the parameters.
|
||||
The `mediaQuery` function uses [`LocalUiMediaScope.current`](https://developer.android.com/reference/kotlin/androidx/compose/ui/package-summary#LocalUiMediaScope())
|
||||
to access the `UiMediaScope` object,
|
||||
which represents the current device capabilities and context.
|
||||
This object is dynamically updated when any changes are made,
|
||||
such as when the user changes the device posture.
|
||||
The `mediaQuery` function then evaluates the `query` lambda
|
||||
with the updated `UiMediaScope` object and returns a boolean value.
|
||||
For example, the following snippet chooses between `TabletopLayout`
|
||||
and `FlatLayout` based on the `windowPosture` parameter value.
|
||||
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun VideoPlayer(
|
||||
// ...
|
||||
) {
|
||||
// ...
|
||||
if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
|
||||
TabletopLayout()
|
||||
} else {
|
||||
FlatLayout()
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Make a decision based on the window size
|
||||
|
||||
[Window size classes](https://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes) are a set of opinionated viewport breakpoints
|
||||
that help you design, develop, and test adaptive layouts.
|
||||
You can compare the two parameters representing the current window size
|
||||
with the threshold defined in the window size classes.
|
||||
The following example changes the number of panes according to the window width.
|
||||
[`WindowSizeClass`](https://developer.android.com/reference/androidx/window/core/layout/WindowSizeClass) class has constants for the thresholds
|
||||
of window size classes (Figure 1).
|
||||
|
||||
The [`derivedMediaQuery`](https://developer.android.com/reference/kotlin/androidx/compose/ui/derivedMediaQuery.composable#derivedMediaQuery(kotlin.Function1)) function evaluates the `query` lambda
|
||||
and wraps the result in a [`derivedStateOf`](https://developer.android.com/develop/ui/compose/side-effects#derivedstateof).
|
||||
Because `windowWidth` and `windowHeight` can update frequently,
|
||||
call the `derivedMediaQuery` function instead of the `mediaQuery` function
|
||||
when you refer to those parameters in the `query` lambda.
|
||||
|
||||
|
||||
```kotlin
|
||||
val narrowerThanMedium by derivedMediaQuery {
|
||||
windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
|
||||
}
|
||||
val narrowerThanExpanded by derivedMediaQuery {
|
||||
windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
|
||||
}
|
||||
when {
|
||||
narrowerThanMedium -> SinglePaneLayout()
|
||||
narrowerThanExpanded -> TwoPaneLayout()
|
||||
else -> ThreePaneLayout()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
**Figure 1**. Layout is updated according to the window width.
|
||||
|
||||
### Update layout according to the window posture
|
||||
|
||||
The `windowPosture` parameter describes the current window posture
|
||||
as a `UiMediaScope.Posture` object.
|
||||
You can check the current [posture](https://developer.android.com/develop/ui/compose/layouts/adaptive/foldables/learn-about-foldables) by comparing the parameter
|
||||
with the values defined in the `UiMediaScope.Posture` class.
|
||||
The following example switches layout according to the window posture:
|
||||
|
||||
|
||||
```kotlin
|
||||
when {
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Check the precision of the available pointing device
|
||||
|
||||
A high precision pointing device helps users to point a UI element precisely.
|
||||
The precision of a pointing device depends on the device type.
|
||||
|
||||
The `pointerPrecision` parameter describes the precision
|
||||
of the available pointing devices, such as a mouse and touchscreen.
|
||||
There are four values defined in the `UiMediaScope.PointerPrecision` class:
|
||||
[`Fine`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Fine()), [`Coarse`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Coarse()), [`Blunt`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#Blunt()), and [`None`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.PointerPrecision#None()).
|
||||
`None` means that no pointing device is available.
|
||||
The precision ranges from highest to lowest in this order:
|
||||
`Fine`, `Coarse`, and `Blunt`.
|
||||
|
||||
If multiple pointing devices are available and their precisions are different,
|
||||
the parameter is resolved with the highest one.
|
||||
For example, if there are two pointing devices --- a `Fine` precision device and
|
||||
a `Blunt` precision device ---
|
||||
`Fine` is the value of the `pointerPrecision` parameter.
|
||||
|
||||
The following example shows a larger button
|
||||
when the user is using a pointing device with low precision:
|
||||
|
||||
|
||||
```kotlin
|
||||
if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
|
||||
LargeSizeButton()
|
||||
} else {
|
||||
NormalSizeButton()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Check the available keyboard type
|
||||
|
||||
The `keyboardKind` parameter represents the type of the available keyboards:
|
||||
[`Physical`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#Physical()), [`Virtual`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#Virtual()), and [`None`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.KeyboardKind#None()).
|
||||
If an on-screen keyboard is displayed and
|
||||
a hardware keyboard is available at the same time,
|
||||
the parameter is resolved as `Physical`.
|
||||
If neither is detected, `None` is the value of the parameter.
|
||||
The following example shows a message suggesting that users connect a keyboard
|
||||
when no keyboard is detected:
|
||||
|
||||
|
||||
```kotlin
|
||||
if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
|
||||
SuggestKeyboardConnect()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Check if the device supports camera and microphone
|
||||
|
||||
Some devices don't support cameras or microphones.
|
||||
You can check if the device supports a camera and a microphone
|
||||
with the `hasCamera` parameter and the `hasMicrophone` parameter.
|
||||
The following example shows buttons to use with camera and microphone
|
||||
when the device supports them:
|
||||
|
||||
|
||||
```kotlin
|
||||
Row {
|
||||
OutlinedTextField(state = rememberTextFieldState())
|
||||
// Show the MicButton when the device supports a microphone.
|
||||
if (mediaQuery { hasMicrophone }) {
|
||||
MicButton()
|
||||
}
|
||||
// Show the CameraButton when the device supports a camera.
|
||||
if (mediaQuery { hasCamera }) {
|
||||
CameraButton()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
### Adjust UI with the estimated viewing distance
|
||||
|
||||
Viewing distance is a factor that helps determine layout.
|
||||
If the user is using the app from a distance,
|
||||
they would expect the text and UI elements to be bigger.
|
||||
The `viewingDistance` parameter provides an estimate of the viewing distance
|
||||
based on the device type and its typical usage context.
|
||||
|
||||
There are three values defined in the `UiMediaScope.ViewingDistance` class:
|
||||
[`Near`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Near()), [`Medium`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Medium()), and [`Far`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.ViewingDistance#Far()).
|
||||
`Near` means that the screen is in close range,
|
||||
and `Far` means that the device is viewed from a distance.
|
||||
The following example increases the font size when the viewing distance is
|
||||
`Far` or `Medium`:
|
||||
|
||||
|
||||
```kotlin
|
||||
val fontSize = when {
|
||||
mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
|
||||
mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
|
||||
else -> 16.sp
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
## Preview a UI component
|
||||
|
||||
You can call the `mediaQuery` and `derivedMediaQuery` functions in the
|
||||
composable functions to preview UI components.
|
||||
The following snippet chooses between `TabletopLayout`
|
||||
and `FlatLayout` based on the `windowPosture` parameter value.
|
||||
To preview the `TabletopLayout`, the `windowPosture` parameter should be
|
||||
[`UiMediaScope.Posture.Tabletop`](https://developer.android.com/reference/kotlin/androidx/compose/ui/UiMediaScope.Posture#Tabletop()).
|
||||
|
||||
|
||||
```kotlin
|
||||
when {
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `mediaQuery` and `derivedMediaQuery` functions evaluate
|
||||
the given `query` lambda within a `UiMediaScope` object,
|
||||
which is provided as `LocalUiMediaScope.current`.
|
||||
You can override it with the following steps:
|
||||
|
||||
1. Enable the `mediaQuery` function.
|
||||
2. Define a custom object that implements the `UiMediaScope` interface.
|
||||
3. Set the custom object to the `LocalUiMediaScope` with the [`CompositionLocalProvider`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocalProvider.composable#CompositionLocalProvider(androidx.compose.runtime.CompositionLocalContext,kotlin.Function0)) function.
|
||||
4. Call the composable to preview in the content lambda of the `CompositionLocalProvider` function.
|
||||
|
||||
You can preview the `TabletopLayout` with the following example:
|
||||
|
||||
|
||||
```kotlin
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewLayoutForTabletop() {
|
||||
// Step 1: Enable the mediaQuery function
|
||||
ComposeUiFlags.isMediaQueryIntegrationEnabled = true
|
||||
|
||||
val currentUiMediaScope = LocalUiMediaScope.current
|
||||
// Step 2: Define a custom object implementing the UiMediaScope interface.
|
||||
// The object overrides the windowPosture parameter.
|
||||
// The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
|
||||
val uiMediaScope = remember(currentUiMediaScope) {
|
||||
object : UiMediaScope by currentUiMediaScope {
|
||||
override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Set the object to the LocalUiMediaScope.
|
||||
CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
|
||||
// Step 4: Call the composable to preview.
|
||||
when {
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
|
||||
mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
@@ -0,0 +1,114 @@
|
||||
Tools for debugging your Compose UI are available in Android Studio.
|
||||
|
||||
## Layout Inspector
|
||||
|
||||
Layout Inspector lets you inspect a Compose layout inside a running app in an
|
||||
emulator or physical device. You can use the Layout Inspector to check how often
|
||||
a composable is recomposed or skipped, which can help identify issues with your
|
||||
app. For example, some coding errors might force your UI to recompose
|
||||
excessively, which can cause [poor performance](https://developer.android.com/develop/ui/compose/performance).
|
||||
Some coding errors can prevent your UI from recomposing and, therefore,
|
||||
prevent your UI changes from showing up on the screen. If you're new to
|
||||
Layout inspector, check the [guidance](https://developer.android.com/studio/debug/layout-inspector) on how to
|
||||
run it.
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** If you're not seeing Compose components in layout inspector, make sure you are not removing `META-INF/androidx.compose.*.version` files from the APK. These are required for layout inspector to work.
|
||||
|
||||
### Get recomposition counts
|
||||
|
||||
When debugging your Compose layouts, knowing when composables
|
||||
[recompose](https://developer.android.com/develop/ui/compose/mental-model#recomposition) is important in
|
||||
understanding whether your UI is implemented properly. For example, if it's
|
||||
recomposing too many times, your app might be doing more work than is necessary.
|
||||
On the other hand, components that don't recompose when you anticipate them to
|
||||
can lead to unexpected behaviors.
|
||||
|
||||
The Layout Inspector shows you when discrete composables in your layout
|
||||
hierarchy have either recomposed or skipped, as you interact with your app. In
|
||||
Android Studio, your recompositions are highlighted to help you determine
|
||||
where in the UI your composables are recomposing.
|
||||
|
||||
**Figure 1.** Recompositions are highlighted in Layout Inspector.
|
||||
|
||||
The highlighted portion shows a gradient overlay of the composable in the image
|
||||
section of the Layout Inspector, and gradually disappears so that you can get an
|
||||
idea of where in the UI the composable with the highest recompositions can be
|
||||
found. If one composable is recomposing at a higher rate than another
|
||||
composable, then the first composable receives a stronger gradient overlay
|
||||
color. If you double-click a composable in the layout inspector, you're taken to
|
||||
the corresponding code for analysis.
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** To view recomposition counts, make sure your app is using an API level of 29 or higher, and `Compose 1.2.0` or higher. Then, deploy your app as you normally would.
|
||||
|
||||
 **Figure 2.**The composition and skip counter in Layout Inspector.
|
||||
|
||||
Open the **Layout Inspector** window and connect to your app process. In the
|
||||
**Component Tree** , there are two columns that appear next to the layout
|
||||
hierarchy. The first column shows the number of compositions for each node and
|
||||
the second column displays the number of skips for each node. Selecting a
|
||||
composable node shows the dimensions and parameters of the composable, unless
|
||||
it's an inline function, in which case the parameters can't be shown. You can
|
||||
also see similar information in the **Attributes** pane when you select a
|
||||
composable from the **Component Tree** or the **Layout Display**.
|
||||
|
||||
Resetting the count can help you understand recompositions or skips during a
|
||||
specific interaction with your app. If you want to reset the count, click
|
||||
**Reset** near the top of the **Component Tree** pane.
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** If you don't see the new columns in the **Component Tree** pane, you can view them by selecting **Show Recomposition Counts** from the **View Options** menu  near the top of the **Component Tree** pane, as shown in the following image.
|
||||
|
||||

|
||||
|
||||
**Figure 3**. Enable the composition and skip counter in Layout Inspector.
|
||||
|
||||
### Compose semantics
|
||||
|
||||
In Compose, [Semantics](https://developer.android.com/develop/ui/compose/accessibility/semantics) describe your UI in an
|
||||
alternative manner that is understandable for
|
||||
[Accessibility](https://developer.android.com/develop/ui/compose/accessibility) services and for the
|
||||
[Testing](https://developer.android.com/develop/ui/compose/testing) framework. You can use the Layout Inspector
|
||||
to inspect semantic information in your Compose layouts.
|
||||
 **Figure 4.** Semantic information displayed using the Layout Inspector.
|
||||
|
||||
When selecting a Compose node, use the **Attributes** pane to check whether it
|
||||
declares semantic information directly, merges semantics from its children, or
|
||||
both. To quickly identify which nodes include semantics, either declared or
|
||||
merged, use select the **View options** drop-down in the **Component Tree** pane
|
||||
and select **Highlight Semantics Layers**. This highlights only the nodes in the
|
||||
tree that include semantics, and you can use your keyboard to quickly navigate
|
||||
between them.
|
||||
|
||||
## Compose UI Check
|
||||
|
||||
To help you build more adaptive and accessible UIs in Jetpack Compose, Android
|
||||
Studio provides a UI Check mode in Compose Preview. This feature is similar
|
||||
to [Accessibility Scanner](https://developer.android.com/guide/topics/ui/accessibility/testing#accessibility-scanner)
|
||||
for views.
|
||||
|
||||
When you activate Compose UI check mode on a Compose Preview, Android Studio
|
||||
automatically audits your Compose UI and suggests improvements to make your UI
|
||||
more accessible and adaptive. Android Studio checks that your UI works across
|
||||
different screen sizes. In the **Problems** panel, the tool shows the issues
|
||||
that it detects, such as text stretched on large screens or low color contrast.
|
||||
|
||||
To access this feature, click the UI Check icon on Compose Preview:
|
||||
 **Figure 5.** Entry point to UI check mode.
|
||||
|
||||
UI check automatically previews your UI in different configurations and
|
||||
highlights issues found in different configurations. In the **Problems** panel,
|
||||
when you click an issue, you can see the details of the issue, suggested fixes,
|
||||
and the renderings that highlight the area of the issue.
|
||||
 **Figure 6.** UI check mode in action.
|
||||
|
||||
### Fix with AI
|
||||
|
||||
For issues detected in UI Check mode, you can use the AI agent to propose and
|
||||
apply code fixes. Click the **Fix with AI** button on an issue in the
|
||||
**Problems** panel. The agent analyzes the problem and your code to suggest
|
||||
changes that resolve the accessibility or adaptive issue.
|
||||
 **Figure 7.** The agent fixes UI issues in UI Check mode.
|
||||
Reference in New Issue
Block a user