Instructions
Step 1. Isolate viewing content and interactive controls
Examine the target composable (for example, a media player or camera preview):
- Isolate the viewing composable: Extract the visual content displayed on the upright screen (such as video playback or camera preview).
- Isolate the controls composable: Extract the interactive touch elements (such as playback buttons or shutter controls).
Step 2. Select layout strategy
Check whether the composables are identical across configurations:
Do not use
Grid— use conditional layout branching (Option A) when the controls or content differ between layouts, requiring two different composables anyway (for example, a flat layout with simple overlay buttons versus a tabletop layout with an expanded control panel or different parameter controls).Use
Grid(Option B) when the composables (viewing content and controls) are identical in both configurations and only their positions change. For example, a media player with unchanged player and controller composables. The composables overlay in flat posture and split into two rows in tabletop posture.
Step 3. Implement the layout
Option A: Using conditional layout branching (non-Grid)
Use this approach when the controls or content differ between postures, requiring different composables for each configuration.
- Use
mediaQueryfromandroidx.compose.uito observewindowPosture(import androidx.compose.ui.mediaQueryandimport androidx.compose.ui.UiMediaScope). Check for tabletop posture usingmediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }(orPosture.Tabletop). Do not usewindowPosture.isTabletopdirectly. (Note: If not already enabled, setComposeUiFlags.isMediaQueryIntegrationEnabled = truein theApplicationclass). - Retrieve the physical horizontal hinge bounds from the window posture using
currentWindowAdaptiveInfoV2().windowPosture(orcurrentWindowAdaptiveInfo().windowPostureif using an older library version). - In the parent container, translate the hinge bounds from window to local
coordinates using
coordinates.windowToLocal(for example, convertingOffset(0f, hinge.top.toFloat())andOffset(0f, hinge.bottom.toFloat())inonGloballyPositioned). Never assume a 50/50 split when surrounding UI components are present. - Expose the local hinge top and bottom boundaries (for example, using
HorizontalRuleror custom layout modifiers):- Constrain the viewing composable above the fold (
maxHeightbounded byhingeTop). - Constrain and offset the controls below the fold (
maxHeightbounded bycontainerHeight - hingeBottom, placed aty = hingeBottom).
- Constrain the viewing composable above the fold (
- If not in tabletop posture, return the standard flat layout (for example, the viewing composable filling the available space with overlay controls).
Option B: Using Grid
- Define layout area identifiers using an
enumclass. - In
defaultConfig, assign all areas torow = 1, column = 1.Griduses implicit z-indexing, rendering composables declared later in the layout over earlier composables. - In
tabletopConfig, define two rows of0.5fheight and assign the primary content area to row 1 and controls to row 2. - Use
mediaQuery(fromandroidx.compose.ui.mediaQuery) to selecttabletopConfigwhen in tabletop posture (windowPosture == UiMediaScope.Posture.Tabletop), anddefaultConfigotherwise. - Pass the config to
Gridand bind content usingModifier.gridItem(areaId). - Declare the viewing composable before the control composable in the
Gridbody. This verifies that controls render over the viewing content in flat posture.
For a reference implementation of Option B, see the Tabletop posture recipe.