🎉

Androidのedge-to-edge表示対応の作業メモ(Compose向け)

2023/11/01に公開2

はじめに

開発に携わっているアプリでedge-to-edge表示対応を進めているので、その作業メモです。
同じような対応をしようとされている方々の何かしらの助けになれば良いなと思います。

実際の作業

基本的にやる事

  1. ActivityでComponentActivity.enableEdgeToEdge() を呼ぶ
  2. 各Composableに必要に応じて適切にWindowInsetを指定する(ComposeのWindowInsetに関しては、star-zeroさんの記事がオススメ)

enableEdgeToEdgeに関して

以前はActivityでWindowCompat.setDecorFitsSystemWindows(window, false)を呼んで、それとは別にステータスバーなどの色を指定していたと思うのですが、
そこらへんを全部まとめてAPI levelごとに良い感じに設定してくれるのがComponentActivity.enableEdgeToEdge()です。

実際の挙動に関してはKatzさんが下記の記事にまとめて下さっているので、ぜひご覧下さい。(Katzさん、ありがとうございます!)
https://kaleidot.net/component-actiivty-enable-edge-to-edge/

ステータスバーが半透明のままで完全な透明にならないケース

Activityに指定しているandroid:theme
<item name="android:windowTranslucentStatus">true</item>という記述があったら、
<item name="android:windowTranslucentStatus">false</item>に書き換えておきましょう。

WindowInsetに関して

いくつかの画面で見落としがちですが共通して対応が必要だった箇所を上げておきます。

PullRefreshIndicator

ぐるぐる中のインジケーターがステータスバーで隠れてしまわないように、refreshingOffsetを調整します。

自分の場合、下記の様なedge-to-edge表示用のrememberPullRefreshState関数を追加しました。


@Composable
@ExperimentalMaterialApi
fun rememberPullRefreshStateForEdgeToEdgeDisplay(
    refreshing: Boolean,
    onRefresh: () -> Unit,
    refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
    refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
): PullRefreshState {
    val topWindowInsetPadding = WindowInsets.safeDrawing.only(
        WindowInsetsSides.Top,
    ).asPaddingValues().calculateTopPadding()

    return rememberPullRefreshState(
        refreshing = refreshing,
        onRefresh = onRefresh,
        refreshThreshold = refreshThreshold,
        refreshingOffset = topWindowInsetPadding + refreshingOffset,
    )
}

LazyColumn

一番下までスクロールしても最後のアイテムがステータスバーに被ったままになってしまうので、
contentPaddingを調整します。

@Composable
@NonRestartableComposable
fun WindowInsets.calculateBottomPadding(): Dp
    = only(WindowInsetsSides.Bottom).asPaddingValues().calculateBottomPadding()
LazyColumn(
    ...
    contentPadding = PaddingValues(
        bottom = WindowInsets.safeDrawing.calculateBottomPadding(),
    ),    
    ...
)

SnackbarHost

Snackbarがナビゲーションバーに被ってしまうので、位置調整します。

SnackbarHost(
    modifier = Modifier
        .windowInsetsPadding(
            WindowInsets.safeDrawing.only(
                WindowInsetsSides.Bottom +
                    WindowInsetsSides.Horizontal,
            )
        )
    ...
)

WindowInsetsSides.BottomだけではなくWindowInsetsSides.Horizontalも指定しているのは、
世の中には端末が横向きでも画面右端 or 左端に縦でナビゲーションバーを表示する端末が存在するからです。

consumeWindowInsets

レイアウトの組み方によっては明示的にconsumeWindowInsetsを呼んで、
子のComposableで再度同じwindowInsets分のpaddingを設けてしまうのを防がないといけないケースがあります。

例:BottomBarでsafeDrawingなbottomのwindowInsetsは消費するので、コンテンツ表示するComposableではsafeDrawingなbottomのwindowInsetsは無視する。

詳しくは公式ドキュメントのInset consumptionをご覧下さい。

全体的な話

  • gesture navigationと2-button or 3-button navigationの両方で確認する
    • gesture navigationなどナビゲーションバーの高さが引くので気付きにくい問題を、2-button or 3-button navigationでは気付ける
  • 端末が横向きの場合、端末によってナビゲーションバーの表示位置が変わる事があるので考慮が必要
    • 画面下端に横で表示するケースと、画面右端 or 左端に縦で表示するケースがある

edge-to-edge対応してみての感想

  • 一度に確認出来るコンテンツの数が増えるので、個人的には体験が良い
  • 没入感も増した気がする

文中で触れていなかった関連情報

公式ドキュメント(ただし、現時点ではComponentActivity.enableEdgeToEdge()に関する記述無し)
https://developer.android.com/develop/ui/views/layout/edge-to-edge

APIレベルごとの適切なedge-to-edge表示設定の記事で、ComponentActivity.enableEdgeToEdge()の実装元ネタだと思われる
https://medium.com/androiddevelopers/is-your-app-providing-a-backward-compatible-edge-to-edge-experience-2479267073a0

Discussion

tomoya0x00tomoya0x00

LazyColumnはcontentPaddingでbottomにpaddingを足しても、キーボード操作で下までスクロールできない。
現状、LazyColumnそのものはキーボード操作で直接スクロールできず、あくまえLazyColumn内のfocusableなComposableにフォーカスが当たった時に、そこにスクロールする挙動になっている。

なので、キーボード操作を考えるとcontentPaddingでは無く、LazyColumn内の一番下に、foucusableなSpacerを足すのが良さそう。

item("bottom_spacer") {
    Spacer(
        modifier = Modifier
            .fillMaxWidth()
            .windowInsetsPadding(
                WindowInsets.safeDrawing.only(
                    WindowInsetsSides.Bottom
                )
            )
            .focusable()
    )
}

tomoya0x00tomoya0x00

世の中にはランドスケープモードでナビゲーションバーを縦に表示する端末が存在するので、
大体のケースではその画面の階層的にTopとなるComposableに

Modifier.windowInsetsPadding(
    WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)
)

というModifierを付けておいた方が良い。