Android Compose UI 頁面架構 (Scaffold)

Scaffold (鷹架) 是一個實現了 Material Design 基礎佈局結構的 Composable。它提供了標準的槽位 (Slots) 讓我們放置:

  • TopBar (頂部應用程式列)
  • BottomBar (底部導航列)
  • FloatingActionButton (浮動按鈕)
  • Snackbar (提示訊息)
  • Content (主要內容)

基礎結構

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("首頁") },
            navigationIcon = {
                IconButton(onClick = { /* Open drawer */ }) {
                    Icon(Icons.Filled.Menu, contentDescription = "Menu")
                }
            }
        )
    },
    floatingActionButton = {
        FloatingActionButton(onClick = { /* Add item */ }) {
            Icon(Icons.Filled.Add, contentDescription = "Add")
        }
    },
    bottomBar = {
        NavigationBar { // 舊版叫 BottomNavigation
            NavigationBarItem(
                icon = { Icon(Icons.Filled.Home, contentDescription = null) },
                label = { Text("Home") },
                selected = true,
                onClick = { }
            )
            NavigationBarItem(
                icon = { Icon(Icons.Filled.Person, contentDescription = null) },
                label = { Text("Profile") },
                selected = false,
                onClick = { }
            )
        }
    }
) { innerPadding ->
    // 主要內容區域
    // 注意!必須使用 innerPadding,否則內容會被 TopBar/BottomBar 遮住
    Box(modifier = Modifier.padding(innerPadding)) {
        Text("這就是主要內容")
    }
}

TopAppBar 變體

Material 3 提供了多種 TopAppBar:

  • TopAppBar (小型,預設)
  • CenterAlignedTopAppBar (標題置中)
  • MediumTopAppBar (中型,標題會隨捲動縮放)
  • LargeTopAppBar (大型)

要使用這些與捲動連動的行為,需要設定 ScrollBehavior

val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

Scaffold(
    modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
    topBar = {
        MediumTopAppBar(
            title = { Text("My App") },
            scrollBehavior = scrollBehavior
        )
    }
) { ... }

Snackbar (提示訊息)

要在 Scaffold 中顯示 Snackbar,需要 SnackbarHostState

val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()

Scaffold(
    snackbarHost = { SnackbarHost(snackbarHostState) },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show Snackbar") },
            icon = { Icon(Icons.Filled.Info, contentDescription = null) },
            onClick = {
                scope.launch {
                    val result = snackbarHostState.showSnackbar(
                        message = "已刪除項目",
                        actionLabel = "復原"
                    )
                    if (result == SnackbarResult.ActionPerformed) {
                        // 處理復原動作
                    }
                }
            }
        )
    }
) { ... }

小結

Scaffold 是每個頁面 (Screen) 的骨架。它自動幫我們處理了常見的 UI 元素佈局,讓我們能專注於頁面內容的開發。記得一定要正確處理 innerPadding