Here are sample implementations for each Canonical Layout using Jetpack Compose:
1. List-Detail Layout
A common use case is a messaging app where selecting an item from a list displays its details.
@Composable
fun ListDetailLayout(windowSizeClass: WindowSizeClass) {
val items = listOf("Message 1", "Message 2", "Message 3")
var selectedItem by remember { mutableStateOf<String?>(null) }
if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded) {
Row(Modifier.fillMaxSize()) {
ListPane(items, onItemSelected = { selectedItem = it }, Modifier.weight(1f))
selectedItem?.let { DetailPane(it, Modifier.weight(2f)) }
}
} else {
if (selectedItem == null) {
ListPane(items, onItemSelected = { selectedItem = it })
} else {
DetailPane(selectedItem!!) { selectedItem = null }
}
}
}
@Composable
fun ListPane(items: List<String>, onItemSelected: (String) -> Unit, modifier: Modifier = Modifier) {
LazyColumn(modifier.fillMaxSize()) {
items(items) { item ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.clickable { onItemSelected(item) }
.padding(16.dp)
)
}
}
}
@Composable
fun DetailPane(item: String, onBack: (() -> Unit)? = null, modifier: Modifier = Modifier) {
Column(modifier.fillMaxSize().padding(16.dp)) {
if (onBack != null) {
Button(onClick = onBack) { Text("Back") }
}
Text(text = "Detail of $item", fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
}
2. Feed Layout
A news or social media app where content is displayed in a flexible grid.
@Composable
fun FeedLayout(windowSizeClass: WindowSizeClass) {
val columns = when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Expanded -> 3
WindowWidthSizeClass.Medium -> 2
else -> 1
}
LazyVerticalGrid(columns = GridCells.Fixed(columns), modifier = Modifier.fillMaxSize()) {
items(20) { index ->
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
elevation = 4.dp
) {
Column(Modifier.padding(16.dp)) {
Text(text = "Item $index", fontWeight = FontWeight.Bold)
Text(text = "Description for item $index")
}
}
}
}
}
3. Supporting Pane Layout
A media player with a primary content area (video) and a supporting pane (related videos).
@Composable
fun SupportingPaneLayout(windowSizeClass: WindowSizeClass) {
if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded) {
Row(Modifier.fillMaxSize()) {
PrimaryContent(Modifier.weight(2f))
SupportingPane(Modifier.weight(1f))
}
} else {
Column(Modifier.fillMaxSize()) {
PrimaryContent(Modifier.weight(3f))
SupportingPane(Modifier.weight(1f))
}
}
}
@Composable
fun PrimaryContent(modifier: Modifier) {
Box(
modifier
.fillMaxSize()
.background(Color.Black),
contentAlignment = Alignment.Center
) {
Text(text = "Main Content (Video)", color = Color.White)
}
}
@Composable
fun SupportingPane(modifier: Modifier) {
LazyColumn(modifier.fillMaxSize().padding(8.dp)) {
items(10) { index ->
Text(text = "Related Video $index", modifier = Modifier.padding(8.dp))
}
}
}
How to Use These Layouts
- Get the window size class:
val windowSizeClass = calculateWindowSizeClass(activity)
- Pass it to your layout:
SupportingPaneLayout(windowSizeClass)
Each layout adapts dynamically to different screen sizes, ensuring an optimal user experience across devices.