NavigableListDetailPaneScaffold

Functions summary

Unit
@ExperimentalMaterial3AdaptiveApi
@Composable
<T : Any?> NavigableListDetailPaneScaffold(
    navigator: ThreePaneScaffoldNavigator<T>,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)?,
    defaultBackBehavior: BackNavigationBehavior,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)?,
    paneExpansionState: PaneExpansionState?
)

A version of ListDetailPaneScaffold that supports navigation and predictive back handling out of the box, controlled by ThreePaneScaffoldNavigator.

android

Functions

@ExperimentalMaterial3AdaptiveApi
@Composable
fun <T : Any?> NavigableListDetailPaneScaffold(
    navigator: ThreePaneScaffoldNavigator<T>,
    listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit,
    modifier: Modifier = Modifier,
    extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null,
    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange,
    paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
    paneExpansionState: PaneExpansionState? = null
): Unit

A version of ListDetailPaneScaffold that supports navigation and predictive back handling out of the box, controlled by ThreePaneScaffoldNavigator.

Example usage, including integration with the Compose Navigation library:

import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

val welcomeRoute = "welcome"
val listDetailRoute = "listdetail"

val coroutineScope = rememberCoroutineScope()

// `navController` handles navigation outside the ListDetailPaneScaffold,
// and `scaffoldNavigator` handles navigation within it. The "content" of
// the scaffold uses a custom type which tracks the index of the selected item,
// which is passed as a type argument to `rememberListDetailPaneScaffoldNavigator`.
val navController = rememberNavController()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<NavItemData>()

// Back behavior can be customized based on the needs of the app.
var backBehaviorIndex by rememberSaveable { mutableStateOf(0) }
val backBehaviors =
    listOf(
        BackNavigationBehavior.PopUntilScaffoldValueChange,
        BackNavigationBehavior.PopUntilCurrentDestinationChange,
        BackNavigationBehavior.PopUntilContentChange,
        BackNavigationBehavior.PopLatest,
    )
val backBehavior = backBehaviors[backBehaviorIndex]

val items = listOf("Item 1", "Item 2", "Item 3")
val extraItems = listOf("Extra 1", "Extra 2", "Extra 3")

NavHost(
    navController = navController,
    startDestination = welcomeRoute,
    enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
    exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
    popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
    popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
) {
    composable(welcomeRoute) {
        Scaffold(Modifier.fillMaxSize()) { paddingValues ->
            Column(
                modifier =
                    Modifier.verticalScroll(rememberScrollState())
                        .padding(paddingValues)
                        .padding(24.dp)
                        .fillMaxSize(),
                verticalArrangement = Arrangement.spacedBy(16.dp),
            ) {
                Text(
                    text = "How should the scaffold handle back navigation?",
                    style = MaterialTheme.typography.headlineMedium,
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 0,
                    onClick = { backBehaviorIndex = 0 },
                    text =
                        "PopUntilScaffoldValueChange - Back navigation forces a change in " +
                            "which pane(s) is/are shown.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 1,
                    onClick = { backBehaviorIndex = 1 },
                    text =
                        "PopUntilCurrentDestinationChange - Back navigation forces a " +
                            "change in which pane is currently considered \"active\".",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 2,
                    onClick = { backBehaviorIndex = 2 },
                    text =
                        "PopUntilContentChange - Back navigation forces a change in the " +
                            "content of any pane or which pane(s) is/are shown.\nNote: this " +
                            "may result in unintuitive behavior if the device size changes " +
                            "in the middle of the navigation.",
                )
                RadioButtonRow(
                    selected = backBehaviorIndex == 3,
                    onClick = { backBehaviorIndex = 3 },
                    text =
                        "PopLatest - No special back handling.\nNote: this may result in " +
                            "unintuitive behavior if the device size changes in the middle " +
                            "of the navigation.",
                )
                Button(onClick = { navController.navigate(listDetailRoute) }) { Text("Next") }
            }
        }
    }
    composable(listDetailRoute) {
        val selectedItem = scaffoldNavigator.currentDestination?.contentKey
        NavigableListDetailPaneScaffold(
            navigator = scaffoldNavigator,
            defaultBackBehavior = backBehavior,
            listPane = {
                AnimatedPane(Modifier.preferredWidth(200.dp)) {
                    ListPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            detailPane = {
                AnimatedPane {
                    DetailPaneContent(
                        items = items,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        hasExtraPane = true,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
            extraPane = {
                AnimatedPane {
                    ExtraPaneContent(
                        extraItems = extraItems,
                        selectedItem = selectedItem,
                        scaffoldNavigator = scaffoldNavigator,
                        backBehavior = backBehavior,
                        coroutineScope = coroutineScope,
                    )
                }
            },
        )
    }
}
Parameters
navigator: ThreePaneScaffoldNavigator<T>

The navigator instance to navigate through the scaffold.

listPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the list pane of the scaffold, which is supposed to hold a list of item summaries that can be selected from, for example, the inbox mail list of a mail app. See ListDetailPaneScaffoldRole.List. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

detailPane: @Composable ThreePaneScaffoldPaneScope.() -> Unit

the detail pane of the scaffold, which is supposed to hold the detailed info of a selected item, for example, the mail content currently being viewed. See ListDetailPaneScaffoldRole.Detail. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

modifier: Modifier = Modifier

Modifier of the scaffold layout.

extraPane: (@Composable ThreePaneScaffoldPaneScope.() -> Unit)? = null

the extra pane of the scaffold, which is supposed to hold any supplementary info besides the list and the detail panes, for example, a task list or a mini-calendar view of a mail app. See ListDetailPaneScaffoldRole.Extra. Note that we suggest you to use AnimatedPane as the root layout of panes, which supports default pane behaviors like enter/exit transitions.

defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange

the default back navigation behavior when the system back event happens. See BackNavigationBehavior for the use cases of each behavior.

paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null

the pane expansion drag handle to allow users to drag to change pane expansion state, null by default.

paneExpansionState: PaneExpansionState? = null

the state object of pane expansion; when no value is provided but paneExpansionDragHandle is not null, a default implementation will be created for the drag handle to use.