ADAPTIVE UI
One screen, every window. Folds too.
Pantrix reads the window, not the device: its width class, its height, a foldable's posture and the pointer. The scaffolds move the navigation, split the panes and meet at the fold by themselves. Pick a window below and watch one app lay itself out.
widthClass
Large
heightClass
Medium
posture
Flat
isNarrow
false
isShort
false
paneCount()
2
layout.margin
32dp
layout.gutter
24dp
columns
12
navigation
Start · sidebar
Why it looks like this: The list and detail scaffold measures its own box, not the window: 1032dp beside the sidebar is Expanded, so 2 panes.
Five widths, three heights
The breakpoints are Android's window size classes, the same on every platform. What is measured is the window: an app in a split screen, a resized desktop window or a browser tab is as wide as that window. The marker is the window chosen above.
Compact
under 600dp
Medium
600–839
Expanded
840–1199
Large
1200–1599
ExtraLarge
1600 and up
Laptop · 1280dp
Height Compact
under 480dp
A phone on its side: no bar at the bottom, the main action in the header.
Height Medium
480–899dp
A phone upright, a tablet on its side.
Height Expanded
900dp and up
A tablet upright, a desktop window.
The window comes with the theme
No wrapper, no setup. Every screen under PantrixTheme finds the window ready, with the page's margin and gutter for its class.
val window = PantrixTheme.window
window.widthClass // Compact … ExtraLarge
window.heightClass // Compact, Medium, Expanded
window.posture.isBook // half open, the hinge upright
window.posture.isTabletop // half open, the hinge across
window.paneCount() // 1, 2 or 3 side by side
PantrixTheme.layout.margin // 16, 24 or 32dp
PantrixTheme.layout.gutter // 16 or 24dp
val columns = window.byWidth(compact = 2, medium = 4)
val panes = window.byPosture(flat = 1, book = 2)
if (window.widthClass >= WindowWidthClass.Expanded) DetailPane()
Which helper, when
You need
Write
A value by width
window.byWidth(compact = 2, medium = 4)
The same in a composable
responsive(compact = 1, medium = 2, large = 3)
The page’s margin and gutter
PantrixTheme.layout.margin / .gutter
A value by height
window.byHeight(compact = 44.dp, medium = 56.dp)
A value by posture
window.byPosture(flat = 1, book = 2, tabletop = 2)
"The phone layout?"
window.isNarrow
"A bar fits at the bottom?"
window.isShort
From this class on
widthClass >= WindowWidthClass.Expanded
A threshold of your own
rememberWindowIsAtLeast(1000.dp)
The component’s own room
BoxWithPantrixWindow { box -> … }
A finger or a mouse
PantrixTheme.platform.isTouch
Six patterns, ready made
Each is a component that measures its own room, keeps its state while it moves, and knows the fold.
1 · PantrixScaffold
Navigation that moves with the width
Along the bottom while the scaffold is narrower than 600dp, along the start edge from there on. On a foldable set on a table it stays at the bottom, where the hands are. The scaffold measures its own width, not the window's.
PantrixScaffold(navigation = { placement, insets ->
when (placement) {
ScaffoldNavigationPlacement.Bottom -> PantrixNavigationBar(items, selected, onSelect, insets = insets)
else -> PantrixNavigationRail(items, selected, onSelect, insets = insets)
}
}) { padding -> Content(Modifier.padding(padding)) }
2 · PantrixListDetailScaffold
List and detail
An inbox, settings, contacts. In one pane the detail slides in over the list and the back gesture brings it back, Android's predictive back and iOS's edge swipe included. The focus follows the pane, and the list's width can be dragged.
val inbox = rememberListDetailState()
PantrixListDetailScaffold(
state = inbox,
list = { Messages(onOpen = { id -> inbox.select(id) }) },
detail = { id -> Message(id) },
extra = { id -> History(id) },
resizable = true
)
3 · PantrixSupportingPaneScaffold
Main content and a supporting pane
A document and its comments, a chart and its filters. Beside the content where there is room; in a narrow room a sheet from the bottom on a phone and from the end on a tablet, or the content's own place. A comment typed stays while the pane moves.
val comments = rememberSupportingPaneState()
PantrixSupportingPaneScaffold(
state = comments,
main = { Document(onComments = { comments.show() }) },
supporting = { Comments() },
supportingTitle = stringResource(Res.string.comments),
presentation = SupportingPanePresentation.Sheet
)
4 · PantrixTwoPane · PantrixTabletopLayout
Folds: two panes and the tabletop
Two panes part exactly at a hinge that separates them, and the hinge's strip stays empty. On a table, what is looked at fills the half above the fold and the controls take the half below. Choose a foldable in the preview above to see both.
PantrixTabletopLayout(
content = { Video(player) },
controls = { PlayerControls(player, large = arrangement == TabletopArrangement.Tabletop) },
flatControls = TabletopControls.Overlay
)
5 · PantrixTheme.layout
Columns, margins and the content's width
Margins of 16, 24 and 32dp, gutters of 16 and 24dp, and 4, 8 or 12 columns come with the class. A part spans columns; nothing is chosen by hand.
val layout = PantrixTheme.layout
Row(Modifier.maxContentWidth(), horizontalArrangement = Arrangement.spacedBy(layout.gutter)) {
Article(Modifier.width(layout.spanWidth(maxWidth, span = 8)))
Related(Modifier.width(layout.spanWidth(maxWidth, span = 4)))
}
6 · BoxWithPantrixWindow
A component asks its own room
The window is for decisions about the page. A component inside a narrow panel would draw its wide layout if it asked the window: it asks the box it is given instead, with the window's breakpoints.
BoxWithPantrixWindow(modifier) { box ->
if (box.isNarrow) OrderList(orders)
else OrderGrid(orders, showDate = box.widthClass >= WindowWidthClass.Expanded)
}
A fold in a preview and a test
PantrixWindowFrame makes a box a window of its own, with a hinge measured from its corner. The demo's cards draw the iPhone Duo's inner screen this way, as a book and on a table.
val book = PantrixWindow(
DpSize(951.dp, 669.dp),
posture = WindowPosture(listOf(WindowFold(
bounds = hinge, orientation = FoldOrientation.Vertical,
isHalfOpened = true, isSeparating = true,
isOccluding = false, clearance = strip
)))
)
PantrixWindowFrame(book, Modifier.size(951.dp, 669.dp)) { InboxScreen() }
Pitfalls
Not this
Calling the same content from two places by class
But this
Call it from one place; give what changes by parameter, or move it
Not this
Asking the window inside a component
But this
Ask its own box with BoxWithPantrixWindow
Not this
Reading window.size for a layout
But this
Read the class; a threshold of your own with rememberWindowIsAtLeast
Not this
Choosing the margin by hand: responsive(16.dp, 24.dp, 32.dp)
But this
PantrixTheme.layout.margin
Not this
Taking isNarrow for "a phone"
But this
A phone on its side is Expanded: isShort tells it
Not this
Looking at the platform for "touch"
But this
PantrixTheme.platform.isTouch: a phone’s browser is the web
Apache License 2.0
English
·