AndroidアプリのEdge-to-edge対応メモ

こんにちは、トリです。
10月16日にAndroid15の配信が開始されました。

Android15から「Edge-to-edge」がデフォルトで適用されます。

Android 15 搭載デバイスでは、デフォルトでアプリがエッジ ツー エッジ対応になります。 Android 15(API レベル 35)がターゲットとなります。

https://developer.android.com/about/versions/15/behavior-changes-15?hl=ja#window-insets

対応のために試行錯誤したので、備忘録として簡単に調査内容をまとめておこうと思います。

View ベースの場合

公式曰く、Material Components (com.google.android.material)を使用している場合、一部を除き追加作業は不要とのことです。
しかし、Material Componentsを使用せずレイアウトしている場合、あるいはBottomSheetなどは、ViewCompat.setOnApplyWindowInsetsListener を使用してPaddingを設定する必要があります。

Material Components使用のレイアウトでやること

「AppBarLayout」があれば「android:fitsSystemWindows="true"」を追加します。

<com.google.android.material.appbar.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:fitsSystemWindows="true">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:elevation="4dp" />
</com.google.android.material.appbar.AppBarLayout>

上記に該当しないレイアウトでやること

「ViewCompat.setOnApplyWindowInsetsListener」でWindowInsetsを取得し、ステータスバーやナビゲーションバーなどのサイズを取得してPaddingを設定します。

ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
    val bars = windowInsets.getInsets(
        WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
    )
    v.updatePadding(
        left = bars.left,
        top = bars.top,
        right = bars.right,
        bottom = bars.bottom,
    )
    WindowInsetsCompat.CONSUMED
}

ナビゲーションバーについて

ナビゲーションバーは2種類あるので、それぞれテストしておくと良さそうです。

  • 3ボタンナビゲーション

  • ジェスチャーハンドルのナビゲーション

ナビゲーションバー2種

BottomSheetDialogFragmentでの悩み

「ViewCompat.setOnApplyWindowInsetsListener」でPaddingを設定しましたが、スクロール可能な状態だとステータスバーに重なってしまいました。
すぐ解決できそうになかったので、一旦下記のコードでステータスバーに重ならない、かつナビゲーションバーはEdge-to-edgeになるよう対処しました。

themes.xml

<style name="AppTheme" parent="@style/Theme.MaterialComponents.Light.DarkActionBar">
    ...
    <item name="bottomSheetDialogTheme">@style/MaterialBottomSheetDialogTheme</item>
</style>


<style name="MaterialBottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
    ...
    <item name="enableEdgeToEdge">false</item>
</style>

MyBottomSheetDialogFragment.kt

class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val bottomSheetDialog = object : BottomSheetDialog(requireContext(), theme) {
            override fun onAttachedToWindow() {
                super.onAttachedToWindow()

                window?.let {
                    WindowCompat.setDecorFitsSystemWindows(it, false)
                }

                findViewById<View>(com.google.android.material.R.id.container)?.apply {
                    ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
                        insets.apply {
                            val bars = getInsets(WindowInsetsCompat.Type.systemBars())
                            view.updatePadding(top = bars.top)
                        }
                    }
                }
            }
        }
        return bottomSheetDialog
    }
    
    // ...
}


Composeベースの場合

Material3のScaffoldやTopAppBarは、デフォルトでWindowInsetsが設定されています。Material2は、必要に応じて手動で設定する必要がありそうです。

Material3のScaffold使用のコンポーザブルでやること

ScaffoldのcontentWindowInsetsはデフォルトの場合、「WindowInsetsCompat.Type.systemBars()」が設定されています。
systemBarsは「statusBars」「navigationBars」「captionBar」を含みます。
端末を縦向きのみで使用するのであれば、デフォルトのままで問題ないと思います。

Scaffold(
    modifier = Modifier,
) { paddingValues ->
    Box(
        modifier = Modifier
            .padding(paddingValues)
            .fillMaxSize(),
    ) {
        LazyColumn {
            ...
        }
    }
}

たとえば、端末の横向き対応(DisplayCutoutへの対処)、キーボード入力を想定し、「safeCustomDrawing」という拡張関数を作成して、contentWindowInsetsに設定できます。

Scaffold(
    modifier = Modifier,
    contentWindowInsets = WindowInsets.safeCustomDrawing,
) { paddingValues ->
    Box(
        modifier = Modifier
            .padding(paddingValues)
            .fillMaxSize(),
    ) {
        LazyColumn {
            ...
        }
    }
}

val WindowInsets.Companion.safeCustomDrawing: WindowInsets
    @Composable
    get() = WindowInsets.statusBars
        .union(WindowInsets.captionBar)
        .union(WindowInsets.ime)
        .union(WindowInsets.displayCutout)

navigationBars分のBottomPaddingは、LazyColumnの末尾に設定することで、ナビゲーションバーエリアまでコンテンツを広く描画できます。

LazyColumn {
    items(list) { ... }
    item {
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
    }
}

ModifierでWindowInsetsを設定したい

「Modifier.statusBarsPadding()」や「Modifier.navigationBarsPadding()」などWindowInsetsタイプごとに用意されているので、必要に応じて利用できます。

参考

この記事が気に入ったらサポートをしてみませんか?