androidkotlinandroid-jetpack-composematerial-design

ModalDrawerSheet is not scrollable


I have implemented navigation using Android jetpack libraries and Material3 design, namely ModalNavigationDrawer and ModalDrawerSheet components. So far so good, however when rotating the screen, not all NavigationDrawerItems are visible, and scrolling is not enabled.

Shouldn't scrolling in ModalDrawerSheetbe always enabled? If not, how can I enable it?

val navigationItems = listOf(
    MainNavigationScreen.Screen1,
    MainNavigationScreen.Screen2,
    MainNavigationScreen.Screen3,
    ...
)
val drawerState = rememberDrawerState(initialValue = DrawerValue.Open)
ModalNavigationDrawer(
    drawerState = drawerState,
    drawerContent = {
        ModalDrawerSheet {
            Spacer(Modifier.height(12.dp))
            Image(
                painter = painterResource(id = R.drawable.ic_top_bar_logo),
                contentDescription = stringResource(id = R.string.app_name),
                modifier = Modifier.fillMaxWidth()
            )
            Spacer(Modifier.height(12.dp))
            HorizontalDivider()
            Spacer(Modifier.height(12.dp))
            navigationItems.forEach {
                NavigationDrawerItem(
                    label = { Text(stringResource(id = it.nameResourceId)) },
                    icon = {
                        Icon(painterResource(id = it.iconResourceId), null)
                    },
                    onClick = {},
                    selected = false,
                    modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                )
            }
            Spacer(Modifier.height(12.dp))
        }
    }) {}

Solution

  • You can use the relevant scrolling modifiers (as documented in the scrolling docs - verticalScroll or horizontalScroll) on the ModalDrawerSheet composable as you would normally:

    ModalDrawerSheet(modifier = Modifier.verticalScroll(rememberScrollableState()) {
      // Drawer content...
    }
    

    Alternatively, you can use the relevant lazy list composable (LazyColumn or LazyRow) as desired:

    ModalDrawerSheet {
      LazyColumn {
        item { /* TODO */ }
        items(...) { /* TODO */ }
      }
    }
    

    Internally, the ModalDrawerSheet composable uses a shared DrawerSheet composable which then uses a Surface consisting of a Column:

    @Composable
    internal fun DrawerSheet(
        drawerPredictiveBackState: DrawerPredictiveBackState?,
        windowInsets: WindowInsets,
        modifier: Modifier = Modifier,
        drawerShape: Shape = RectangleShape,
        drawerContainerColor: Color = DrawerDefaults.standardContainerColor,
        drawerContentColor: Color = contentColorFor(drawerContainerColor),
        drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
        drawerOffset: () -> Float = { 0F },
        content: @Composable ColumnScope.() -> Unit
    ) {
        val density = LocalDensity.current
        val maxWidth = NavigationDrawerTokens.ContainerWidth
        val maxWidthPx = with(density) { maxWidth.toPx() }
        val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
        val predictiveBackDrawerContainerModifier =
            if (drawerPredictiveBackState != null) {
                Modifier.predictiveBackDrawerContainer(drawerPredictiveBackState, isRtl)
            } else {
                Modifier
            }
        Surface(
            modifier =
                modifier
                    .sizeIn(minWidth = MinimumDrawerWidth, maxWidth = maxWidth)
                    // Scale up the Surface horizontally in case the drawer offset it greater than zero.
                    // This is done to avoid showing a gap when the drawer opens and bounces when it's
                    // applied with a bouncy motion. Note that the content inside the Surface is scaled
                    // back down to maintain its aspect ratio (see below).
                    .horizontalScaleUp(
                        drawerOffset = drawerOffset,
                        drawerWidth = maxWidthPx,
                        isRtl = isRtl
                    )
                    .then(predictiveBackDrawerContainerModifier)
                    .fillMaxHeight(),
            shape = drawerShape,
            color = drawerContainerColor,
            contentColor = drawerContentColor,
            tonalElevation = drawerTonalElevation
        ) {
            val predictiveBackDrawerChildModifier =
                if (drawerPredictiveBackState != null)
                    Modifier.predictiveBackDrawerChild(drawerPredictiveBackState, isRtl)
                else Modifier
            Column(
                Modifier.sizeIn(minWidth = MinimumDrawerWidth, maxWidth = maxWidth)
                    // Scale the content down in case the drawer offset is greater than one. The
                    // wrapping Surface is scaled up, so this is done to maintain the content's aspect
                    // ratio.
                    .horizontalScaleDown(
                        drawerOffset = drawerOffset,
                        drawerWidth = maxWidthPx,
                        isRtl = isRtl
                    )
                    .then(predictiveBackDrawerChildModifier)
                    .windowInsetsPadding(windowInsets),
                content = content
            )
        }
    }