マテリアル コンポーネントとレイアウト

Jetpack Compose には、デジタル インターフェースを作成するための包括的なデザイン システムであるマテリアル デザインの実装が用意されています。マテリアル コンポーネント(ボタン、カード、スイッチなど)と Scaffold などのレイアウトは、コンポーズ可能な関数として使用できます。

マテリアル コンポーネントは、ユーザー インターフェースを作成するためのインタラクティブなビルディング ブロックです。Compose には、こうしたコンポーネントが複数用意されており、すぐに使用できます。利用可能なコンポーネントについては、Compose Material API リファレンスをご覧ください。

マテリアル コンポーネントは、アプリで MaterialTheme が提供する値を使用します。

@Composable
fun MyApp() {
    MaterialTheme {
        // Material Components like Button, Card, Switch, etc.
    }
}

テーマ設定について詳しくは、Compose のデザイン システムに関するガイドをご覧ください。

コンテンツ スロット

内側のコンテンツ(テキストラベル、アイコンなど)をサポートするマテリアル コンポーネントは、マテリアルの仕様に合わせて内側のコンテンツのレイアウトをサポートするために、パブリック定数(サイズやパディングなど)だけでなく「スロット」(コンポーズ可能なコンテンツを受け入れる汎用ラムダ)も提供する傾向にあります。

Button がその一例です。

Button(
    onClick = { /* ... */ },
    // Uses ButtonDefaults.ContentPadding by default
    contentPadding = PaddingValues(
        start = 20.dp,
        top = 12.dp,
        end = 20.dp,
        bottom = 12.dp
    )
) {
    // Inner content including an icon and a text label
    Icon(
        Icons.Filled.Favorite,
        contentDescription = "Favorite",
        modifier = Modifier.size(ButtonDefaults.IconSize)
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

図 1. content スロットとデフォルトのパディングを使用する Button(左)と、カスタム contentPadding を提供する content スロットを使用する Button(右)。

Button には汎用の content 後置ラムダスロットがあり、RowScope を使用してコンテンツのコンポーザブルを一列にレイアウトします。また、内側のコンテンツにパディングを適用するための contentPadding パラメータもあります。ButtonDefaults で提供される定数またはカスタム値を使用できます。

別の例として、ExtendedFloatingActionButton が挙げられます。

ExtendedFloatingActionButton(
    onClick = { /* ... */ },
    icon = {
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite"
        )
    },
    text = { Text("Like") }
)

図 2. icon スロットと text スロットを使用する ExtendedFloatingActionButton

ExtendedFloatingActionButton には、汎用の content ラムダの代わりに、icontext ラベル用のスロットが 2 つあります。各スロットは汎用のコンポーズ可能なコンテンツをサポートしていますが、コンポーネントはこうした内側のコンテンツのレイアウト方法を決定します。パディング、アライメント、サイズは内部で処理されます。

Scaffold

Compose には、マテリアル コンポーネントを一般的な画面パターンに組み合わせるための便利なレイアウトが用意されています。Scaffold などのコンポーザブルは、さまざまなコンポーネントやその他の画面要素用のスロットを提供します。

画面のコンテンツ

Scaffold には汎用の content 後置ラムダスロットがあります。このラムダは、トップバーとボトムバーがある場合、それをオフセットするために(たとえば Modifier.padding を介して)コンテンツ ルートに適用される PaddingValues のインスタンスを受け取ります。

Scaffold(/* ... */) { contentPadding ->
    // Screen content
    Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}

アプリバー

Scaffold は、トップ アプリバーまたはボトム アプリバー用のスロットを提供します。コンポーザブルの配置は内部で処理されます。

topBar スロットと TopAppBar を使用できます。

Scaffold(
    topBar = {
        TopAppBar { /* Top app bar content */ }
    }
) {
    // Screen content
}

bottomBar スロットと BottomAppBar を使用できます。

Scaffold(
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

これらのスロットは、BottomNavigation などの他のマテリアル コンポーネントに使用できます。カスタム コンポーザブルも使用できます。たとえば Owl サンプルのオンボーディング画面をご覧ください。

フローティング操作ボタン

Scaffoldフローティング操作ボタン用のスロットを提供します。

floatingActionButton スロットと FloatingActionButton を使用できます。

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    }
) {
    // Screen content
}

FAB コンポーザブルの下部配置は内部で処理されます。floatingActionButtonPosition パラメータを使用すると水平方向の位置を調整できます。

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to FabPosition.End
    floatingActionButtonPosition = FabPosition.Center
) {
    // Screen content
}

Scaffold コンポーザブルの bottomBar スロットを使用している場合は、isFloatingActionButtonDocked パラメータを使用すると FAB をボトム アプリバーと重ねることができます。

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to false
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

図 3. floatingActionButton スロットと bottomBar スロットを使用する ScaffoldisFloatingActionButtonDocked パラメータを false(上部)と true(下部)に設定しています。

BottomAppBar は、任意の Shape を受け入れる cutoutShape パラメータで FAB カットアウトをサポートしています。ドッキングしたコンポーネントで使用されているものと同じ Shape を指定することをおすすめします。たとえば次の FloatingActionButton は、shape パラメータのデフォルト値として、コーナーサイズ 50% の MaterialTheme.shapes.small を使用しています。

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar(
            // Defaults to null, that is, No cutout
            cutoutShape = MaterialTheme.shapes.small.copy(
                CornerSize(percent = 50)
            )
        ) {
            /* Bottom app bar content */
        }
    }
) {
  // Screen content
}

図 4. BottomAppBar とドッキングした FloatingActionButton を持つ ScaffoldBottomAppBar には、FloatingActionButton で使用されている Shape と一致するカスタム cutoutShape があります。

スナックバー

Scaffoldスナックバーを表示する手段を提供します。

これは、SnackbarHostState プロパティを含む ScaffoldState を介して提供されます。rememberScaffoldState を使用して、scaffoldState パラメータで Scaffold に渡す必要のある ScaffoldState のインスタンスを作成できます。SnackbarHostState は、showSnackbar 関数へのアクセスを提供します。この suspend 関数は CoroutineScope を必要とし(たとえば rememberCoroutineScope を使用)、UI イベントに応じて呼び出され、Scaffold 内に Snackbar を表示します。

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    scaffoldState.snackbarHostState
                        .showSnackbar("Snackbar")
                }
            }
        )
    }
) {
    // Screen content
}

任意のアクションを指定し、Snackbar の継続時間を調整できます。snackbarHostState.showSnackbar 関数は追加の actionLabel パラメータと duration パラメータを受け入れ、SnackbarResult を返します。

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    val result = scaffoldState.snackbarHostState
                        .showSnackbar(
                            message = "Snackbar",
                            actionLabel = "Action",
                            // Defaults to SnackbarDuration.Short
                            duration = SnackbarDuration.Indefinite
                        )
                    when (result) {
                        SnackbarResult.ActionPerformed -> {
                            /* Handle snackbar action performed */
                        }
                        SnackbarResult.Dismissed -> {
                            /* Handle snackbar dismissed */
                        }
                    }
                }
            }
        )
    }
) {
    // Screen content
}

snackbarHost パラメータでカスタムの Snackbar を指定できます。詳しくは SnackbarHost API reference docs をご覧ください。

ドロワー

Scaffoldモーダル ナビゲーション ドロワー用のスロットを提供します。ドラッグ可能なシートとコンポーザブルのレイアウトは内部で処理されます。

drawerContent スロットでは、ColumnScope を使用してドロワー コンテンツのコンポーザブルを一列にレイアウトできます。

Scaffold(
    drawerContent = {
        Text("Drawer title", modifier = Modifier.padding(16.dp))
        Divider()
        // Drawer items
    }
) {
    // Screen content
}

Scaffold は他にさまざまなドロワー パラメータを受け入れます。たとえば drawerGesturesEnabled パラメータで、ドロワーがドラッグに応答するかどうかを切り替えることができます。

Scaffold(
    drawerContent = {
        // Drawer content
    },
    // Defaults to true
    drawerGesturesEnabled = false
) {
    // Screen content
}

プログラムによるドロワーの開閉は ScaffoldState を介して行います。これには scaffoldState パラメータを使用して Scaffold に渡す必要のある DrawerState プロパティが含まれています。DrawerState は、現在のドロワーの状態に関連するプロパティだけでなく、open 関数と close 関数へのアクセスを提供します。これらの suspend 関数は CoroutineScope を必要とし(たとえば rememberCoroutineScope を使用)、UI イベントに応じて呼び出すことができます。

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = {
        // Drawer content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Open or close drawer") },
            onClick = {
                scope.launch {
                    scaffoldState.drawerState.apply {
                        if (isClosed) open() else close()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Scaffold を使用せずにモーダル ナビゲーション ドロワーを実装する場合は、ModalDrawer コンポーザブルを使用できます。Scaffold と同様のドロワー パラメータを使用できます。

val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

ボトム ナビゲーション ドロワーを実装する場合は BottomDrawer コンポーザブルを使用できます。

val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

ボトムシート

標準のボトムシートを実装する場合は BottomSheetScaffold コンポーザブルを使用できます。topBarfloatingActionButtonsnackbarHost など、Scaffold と同様のパラメータを使用できます。これには、ボトムシートを表示する手段を提供する追加のパラメータが含まれています。

sheetContent スロットでは、ColumnScope を使用してシート コンテンツのコンポーザブルを一列にレイアウトできます。

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

BottomSheetScaffold は他にさまざまなシート パラメータを受け入れます。たとえば、sheetPeekHeight パラメータでシートのピーク高さを設定できます。sheetGesturesEnabled パラメータで、ドロワーがドラッグに応答するかどうかを切り替えることもできます。

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    },
    // Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
    sheetPeekHeight = 128.dp,
    // Defaults to true
    sheetGesturesEnabled = false

) {
    // Screen content
}

プログラムによるシートの開閉は BottomSheetScaffoldState を介して行います。これには BottomSheetState プロパティが含まれています。rememberBottomSheetScaffoldState を使用して、scaffoldState パラメータで BottomSheetScaffold に渡す必要のある BottomSheetScaffoldState のインスタンスを作成できます。BottomSheetState は、現在のシートの状態に関連するプロパティだけでなく、expand 関数と collapse 関数へのアクセスを提供します。これらの suspend 関数は CoroutineScope を必要とし(たとえば rememberCoroutineScope を使用)、UI イベントに応じて呼び出すことができます。

val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
    scaffoldState = scaffoldState,
    sheetContent = {
        // Sheet content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Expand or collapse sheet") },
            onClick = {
                scope.launch {
                    scaffoldState.bottomSheetState.apply {
                        if (isCollapsed) expand() else collapse()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

モーダル ボトムシートを実装する場合は ModalBottomSheetLayout コンポーザブルを使用できます。

val sheetState = rememberModalBottomSheetState(
    ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
    sheetState = sheetState,
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

背景

背景を実装する場合は BackdropScaffold コンポーザブルを使用できます。

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)

BackdropScaffold は他にさまざまな背景パラメータを受け入れます。たとえば peekHeight パラメータと headerHeight パラメータで、背面レイヤのピークの高さと、前面レイヤの非アクティブの最小の高さを設定できます。gesturesEnabled パラメータで、背景がドラッグに応答するかどうかを切り替えることもできます。

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    },
    // Defaults to BackdropScaffoldDefaults.PeekHeight
    peekHeight = 40.dp,
    // Defaults to BackdropScaffoldDefaults.HeaderHeight
    headerHeight = 60.dp,
    // Defaults to true
    gesturesEnabled = false
)

プログラムによる背景の表示と非表示は、BackdropScaffoldState を介して行います。rememberBackdropScaffoldState を使用して、scaffoldState パラメータで BackdropScaffold に渡す必要のある BackdropScaffoldState のインスタンスを作成できます。BackdropScaffoldState は、現在の背景の状態に関連するプロパティだけでなく、reveal 関数と conceal 関数へのアクセスを提供します。これらの suspend 関数は CoroutineScope を必要とし(たとえば rememberCoroutineScope を使用)、UI イベントに応じて呼び出すことができます。

val scaffoldState = rememberBackdropScaffoldState(
    BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
    scaffoldState = scaffoldState,
    appBar = {
        TopAppBar(
            title = { Text("Backdrop") },
            navigationIcon = {
                if (scaffoldState.isConcealed) {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.reveal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Menu,
                            contentDescription = "Menu"
                        )
                    }
                } else {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.conceal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Close,
                            contentDescription = "Close"
                        )
                    }
                }
            },
            elevation = 0.dp,
            backgroundColor = Color.Transparent
        )
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)