Material 3で大きく変わったTopAppBarをJetpack Composeで実装してみる

本記事は [Android Advent Calendar 2021](https://qiita.com/advent-calendar/2021/android) の 6 日目の記事です。

Material 3 で TopAppBar のデザインや挙動が変わりました。
Jetpack Compose の Material3 対応版である `androidx.compose.material3` を使用してガイドラインに合わせた実装例をいくつか見ていこうと思います。

※`androidx.compose.material3` は 1.0.0-alpha02 時点の内容なので、ライブラリの更新で内容が変わる可能性もあります。

## TopAppBar のパターン

Material 3 では TopAppBar に以下の 4 つのパターンがあります。

- Center-aligned
- Small
- Medium
- Large

それぞれの実装例を見ていきましょう。

### Center-aligned

![Center-aligned top app bar image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/31c00662-6003-5ea6-458a-58635abdf5f2.png)


`CenterAlignedTopAppBar` の Composable 関数を使います。

```kotlin
@Composable
fun CenterAlignedTopAppBar(
    title: (@Composable () -> Unit)?,
    modifier: Modifier? = Modifier,
    navigationIcon: (@Composable () -> Unit)? = {},
    actions: (@Composable @ExtensionFunctionType RowScope.() -> Unit)? = {},
    colors: TopAppBarColors? = TopAppBarDefaults.centerAlignedTopAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
): Unit
```

`CenterAlignedTopAppBar` をそのまま使用した場合 Surface の色がそのまま適用されます。
Material 3 の TopAppBar はコンテンツのスクロールに合わせて elevation が変わる Lift On Scroll を適用する必要があり、Jetpack Compose では `TopAppBarDefaults.pinnedScrollBehavior` を使用して実装します。

```kotlin
@Composable
fun ExampleScreen() {
    val scrollBehavior = remember { TopAppBarDefaults.pinnedScrollBehavior() }
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            CenterAlignedTopAppBar(
                title = {
                    Text(text = "Title")
                },
                navigationIcon = {
                    BackIconButton {
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = it
        ) {
            ...
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/840eb0b4-efbd-f457-6689-5ad0fa38bd07.gif" width="400px" /> 


### Small

![Small top app bar image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/52cf0dcf-4351-743e-55d2-6989b332fcb8.png)


`SmallTopAppBar` の Composable 関数を使います。

```kotlin
@Composable
fun SmallTopAppBar(
    title: (@Composable () -> Unit)?,
    modifier: Modifier? = Modifier,
    navigationIcon: (@Composable () -> Unit)? = {},
    actions: (@Composable @ExtensionFunctionType RowScope.() -> Unit)? = {},
    colors: TopAppBarColors? = TopAppBarDefaults.smallTopAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
): Unit
```

`CenterAlignedTopAppBar` と同じく、スクロールに合わせて TopAppBar の色を変えるには `TopAppBarDefaults.pinnedScrollBehavior` を使用して以下のように実装する必要があります。

```kotlin
@Composable
fun ExampleScreen() {
    val scrollBehavior = remember { TopAppBarDefaults.pinnedScrollBehavior() }
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            SmallTopAppBar(
                title = {
                    Text(text = "Title")
                },
                navigationIcon = {
                    BackIconButton {
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = it
        ) {
            ...
        }
    }
}
```

また、`TopAppBarDefaults.enterAlwaysScrollBehavior` を使うと上にスクロールすると TopAppBar を隠し下にスクロールさせると TopAppBar を表示させる挙動にすることもできます。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/e68f0cb9-1733-9525-2d43-e527c1ff9cef.gif" width="400px" /> 



### Medium

![Medium top app bar image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/b1d33cec-4a9e-0b31-0e19-e863ef243eb1.png)


`MediumTopAppBar`　の　Composable 関数を使います。

```kotlin
@Composable
fun MediumTopAppBar(
    title: (@Composable () -> Unit)?,
    modifier: Modifier? = Modifier,
    navigationIcon: (@Composable () -> Unit)? = {},
    actions: (@Composable @ExtensionFunctionType RowScope.() -> Unit)? = {},
    colors: TopAppBarColors? = TopAppBarDefaults.mediumTopAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
): Unit
```

Medium の TopAppBar は上にスクロールすると折り畳まれる必要があります。
また Medium の TopAppBar はスクロールが一番上に到達するまで折り畳まれている必要があるので、`TopAppBarDefaults.enterAlwaysScrollBehavior` ではなく `TopAppBarDefaults.exitUntilCollapsedScrollBehavior` を使用することで一番上にスクロールするまで折り畳まれた状態にすることができます。

```kotlin
@Composable
fun ExampleScreen() {
    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
    val scrollBehavior = remember(decayAnimationSpec) {
        TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
    }
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            MediumTopAppBar(
                title = {
                    Text(text = "Title")
                },
                navigationIcon = {
                    BackIconButton {
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = it
        ) {
            ...
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/050d6d9d-064e-a91c-779e-219327431cb7.gif" width="400px" /> 



### Large

![Large top app bar image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/46afd81e-16bc-515e-6c03-0787a543b676.png)


`LargeTopAppBar`　の　Composable 関数を使います。

```kotlin
@Composable
fun LargeTopAppBar(
    title: (@Composable () -> Unit)?,
    modifier: Modifier? = Modifier,
    navigationIcon: (@Composable () -> Unit)? = {},
    actions: (@Composable @ExtensionFunctionType RowScope.() -> Unit)? = {},
    colors: TopAppBarColors? = TopAppBarDefaults.largeTopAppBarColors(),
    scrollBehavior: TopAppBarScrollBehavior? = null
): Unit
```

Medium　と同じくスクロールした時に折り畳まれているようにするため `TopAppBarDefaults.exitUntilCollapsedScrollBehavior` を実装します。


```kotlin
@Composable
fun ExampleScreen() {
    val decayAnimationSpec = rememberSplineBasedDecay<Float>()
    val scrollBehavior = remember(decayAnimationSpec) {
        TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec)
    }
    Scaffold(
        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
        topBar = {
            LargeTopAppBar(
                title = {
                    Text(text = "Title")
                },
                navigationIcon = {
                    BackIconButton {
                    }
                },
                scrollBehavior = scrollBehavior
            )
        }
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = it
        ) {
            ...
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/7a642546-6b32-85f9-3294-05489f2377a8.gif" width="400px" /> 



Material 3 では TopAppBar にいくつか種類が登場し、Jetpack Compose では Lift On Scroll のために少しばかり実装の必要があります。


## 参考

- https://m3.material.io/components/top-app-bar
- https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary