Scaffold
Composable Component
Scaffold implements the basic Material Design visual layout structure.
Common
@Composable
fun Scaffold(
contentWindowInsets: WindowInsets,
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit,
)
Parameters
| contentWindowInsets | window insets to be passed to content slot via PaddingValues params. Scaffold will take the insets into account from the top/bottom only if the topBar/ bottomBar are not present, as the scaffold expect topBar/bottomBar to handle insets instead. Any insets consumed by other insets padding modifiers or consumeWindowInsets on a parent layout will be excluded from contentWindowInsets. |
| modifier | optional Modifier for the root of the Scaffold |
| scaffoldState | state of this scaffold widget. It contains the state of the screen, e.g. variables to provide manual control over the drawer behavior, sizes of components, etc |
| topBar | top app bar of the screen. Consider using TopAppBar. |
| bottomBar | bottom bar of the screen. Consider using BottomAppBar. |
| snackbarHost | component to host Snackbars that are pushed to be shown via SnackbarHostState.showSnackbar. Usually it's a SnackbarHost |
| floatingActionButton | Main action button of your screen. Consider using FloatingActionButton for this slot. |
| floatingActionButtonPosition | position of the FAB on the screen. See FabPosition for possible options available. |
| isFloatingActionButtonDocked | whether floatingActionButton should overlap with bottomBar for half a height, if bottomBar exists. Ignored if there's no bottomBar or no floatingActionButton. |
| drawerContent | content of the Drawer sheet that can be pulled from the left side (right for RTL). |
| drawerGesturesEnabled | whether or not drawer (if set) can be interacted with via gestures |
| drawerShape | shape of the drawer sheet (if set) |
| drawerElevation | drawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set) |
| drawerBackgroundColor | background color to be used for the drawer sheet |
| drawerContentColor | color of the content to use inside the drawer sheet. Defaults to either the matching content color for drawerBackgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface. |
| drawerScrimColor | color of the scrim that obscures content when the drawer is open |
| backgroundColor | background of the scaffold body |
| contentColor | color of the content in scaffold body. Defaults to either the matching content color for backgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface. |
| content | content of your screen. The lambda receives an PaddingValues that should be applied to the content root via androidx.compose.foundation.layout.padding and androidx.compose.foundation.layout.consumeWindowInsets to properly offset top and bottom bars. If using androidx.compose.foundation.verticalScroll, apply this modifier to the child of the scroll, and not on the scroll itself. |
Common
@Composable
fun Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit,
)
Parameters
| modifier | optional Modifier for the root of the Scaffold |
| scaffoldState | state of this scaffold widget. It contains the state of the screen, e.g. variables to provide manual control over the drawer behavior, sizes of components, etc |
| topBar | top app bar of the screen. Consider using TopAppBar. |
| bottomBar | bottom bar of the screen. Consider using BottomAppBar. |
| snackbarHost | component to host Snackbars that are pushed to be shown via SnackbarHostState.showSnackbar. Usually it's a SnackbarHost |
| floatingActionButton | Main action button of your screen. Consider using FloatingActionButton for this slot. |
| floatingActionButtonPosition | position of the FAB on the screen. See FabPosition for possible options available. |
| isFloatingActionButtonDocked | whether floatingActionButton should overlap with bottomBar for half a height, if bottomBar exists. Ignored if there's no bottomBar or no floatingActionButton. |
| drawerContent | content of the Drawer sheet that can be pulled from the left side (right for RTL). |
| drawerGesturesEnabled | whether or not drawer (if set) can be interacted with via gestures |
| drawerShape | shape of the drawer sheet (if set) |
| drawerElevation | drawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set) |
| drawerBackgroundColor | background color to be used for the drawer sheet |
| drawerContentColor | color of the content to use inside the drawer sheet. Defaults to either the matching content color for drawerBackgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface. |
| drawerScrimColor | color of the scrim that obscures content when the drawer is open |
| backgroundColor | background of the scaffold body |
| contentColor | color of the content in scaffold body. Defaults to either the matching content color for backgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface. |
| content | content of your screen. The lambda receives an PaddingValues that should be applied to the content root via androidx.compose.foundation.layout.padding and androidx.compose.foundation.layout.consumeWindowInsets to properly offset top and bottom bars. If using androidx.compose.foundation.verticalScroll, apply this modifier to the child of the scroll, and not on the scroll itself. |
Code Examples
ScaffoldWithBottomBarAndCutout
@Composable
fun ScaffoldWithBottomBarAndCutout() {
val scaffoldState = rememberScaffoldState()
// Consider negative values to mean 'cut corner' and positive values to mean 'round corner'
val sharpEdgePercent = -50f
val roundEdgePercent = 45f
// Start with sharp edges
val animatedProgress = remember { Animatable(sharpEdgePercent) }
// Create a coroutineScope for the animation
val coroutineScope = rememberCoroutineScope()
// animation value to animate shape
val progress = animatedProgress.value.roundToInt()
// When progress is 0, there is no modification to the edges so we are just drawing a rectangle.
// This allows for a smooth transition between cut corners and round corners.
val fabShape =
if (progress < 0) {
CutCornerShape(abs(progress))
} else if (progress == roundEdgePercent.toInt()) {
CircleShape
} else {
RoundedCornerShape(progress)
}
// lambda to call to trigger shape animation
val changeShape: () -> Unit = {
val target = animatedProgress.targetValue
val nextTarget = if (target == roundEdgePercent) sharpEdgePercent else roundEdgePercent
coroutineScope.launch {
animatedProgress.animateTo(
targetValue = nextTarget,
animationSpec = TweenSpec(durationMillis = 600),
)
}
}
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
topBar = { TopAppBar(title = { Text("Scaffold with bottom cutout") }) },
bottomBar = {
BottomAppBar(cutoutShape = fabShape) {
IconButton(
onClick = { coroutineScope.launch { scaffoldState.drawerState.open() } }
) {
Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
}
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Change shape") },
onClick = changeShape,
shape = fabShape,
)
},
contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true,
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
items(count = 100) {
Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
}
}
},
)
}
ScaffoldWithSimpleSnackbar
@Composable
fun ScaffoldWithSimpleSnackbar() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
var clickCount by remember { mutableStateOf(0) }
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
// show snackbar as a suspend function
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Snackbar # ${++clickCount}")
}
},
)
},
contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
content = { innerPadding ->
Text(
text = "Body content",
modifier = Modifier.padding(innerPadding).fillMaxSize().wrapContentSize(),
)
},
)
}
SimpleScaffoldWithTopBar
@Composable
fun SimpleScaffoldWithTopBar() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
topBar = {
TopAppBar(
title = { Text("Simple Scaffold Screen") },
navigationIcon = {
IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) {
Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
},
)
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Inc") },
onClick = { /* fab click handler */ },
)
},
contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
items(count = 100) {
Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
}
}
},
)
}