N予備校 Android アプリで Edge-to-edge を導入した話

#

[N予備校 Android](https://play.google.com/store/apps/details?id=nico.ed.nnn.zane&hl=ja&gl=US&pli=1)チームでテックリードをしている鎌田です。

[N予備校 Android](https://play.google.com/store/apps/details?id=nico.ed.nnn.zane&hl=ja&gl=US&pli=1)チームでは、[Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) を導入しました。

|  Before  |  After  |
| --- | --- |
|  ![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/c0b2f18b-8a27-b70f-ef50-869c71af6d5e.png)  |  ![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/7f806be6-20ef-8222-c025-53bb39864356.png)  |

本記事では、コード例を交えてどのように [Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) を導入したかの手順、導入にあたってつまずいたポイントについてまとめます。

[Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) の概要に関しては、様々なサイトで解説されていますので本記事では説明を割愛します。

## 導入手順

### 1. 全画面描画する設定にする

ホーム画面のアクティビティを HomeActivity とします。
HomeActivity に全画面描画するための処理を実装します。

```kotlin:HomeActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        // TODO: edge-to-edge対応時に有効化する
//        WindowCompat.setDecorFitsSystemWindows(window, false)
        super.onCreate(savedInstanceState)
        ...
    }
```

ただ、いきなり有効化するとツールバーやボトムバーがシステム UI に被ってしまうため、全ての対応が完了するまでは無効化しておきます。

部分的に適用できているか確認する際は、動作確認時に一時的にコメントアウトを外すことで確認してください。

### 2. システムバーの背景色を変更する

themes.xml にて、ナビゲーションバーとステータスバーの色を変更します。

ナビゲーションバーは透過色、ステータスバーはツールバーの色に合わせます。

ツールバーがある関係でステータスバーと被ってしまうと、ツールバーのボタンが押しづらくなるため、後の手順で被らないように設定していきます。

```xml:themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    ...
    <style name="Theme.App.HomeScreen" parent="AppTheme">
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@color/Blue</item>
    </style>
</resources>
```

また、ナビゲーションバー上の要素の色をグレーにします。

android:windowLightNavigationBar は API Level 27 以降でのみ使用可能なため、minSdk が 27 未満の場合、別途 res/values-v27/themes.xml を作成します。

```xml:res/values-v27/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <style name="Theme.App.HomeScreen" parent="AppTheme">
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@color/Blue</item>
        <item name="android:windowLightNavigationBar">true</item>
    </style>
</resources>
```

そして、上記で作成した style を HomeActiivty に適用します。

```xml:AndroidManifest.xml
<activity
    android:name=".HomeActivity"
    android:configChanges="orientation|screenSize"
    android:exported="true"
    android:screenOrientation="portrait"
    android:theme="@style/Theme.App.HomeScreen"
    android:windowSoftInputMode="adjustResize">
    ...
</activity>
```

### 3. システムバーに重なる部分をずらす

Android View と Jetpack Compose とでは実装方法が異なりますので、それぞれの実装方法を記載します。

#### Android View

ViewCompat.setOnApplyWindowInsetsListener を使用することで、システムバーなどのサイズを取得できます。

```kotlin
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    view.updateLayoutParams<MarginLayoutParams>(
        leftMargin = insets.left,
        topMargin = insets.top,
        bottomMargin = insets.bottom,
        rightMargin = insets.right,
    )
 
    // 子ビューに対して設定を適用しない場合に使用する
    WindowInsetsCompat.CONSUMED
}
```

windowInsets.getInsets の引数に取得したいシステムバーを指定します。

- ステータスバーに重なる部分をずらす
  - windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()) を使用します
- ナビゲーションバーに重なる部分をずらす
  - windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()) を使用します
  - キーボードが表示される場合、UI が隠れてしまうため併せて windowInsets.getInsets(WindowInsetsCompat.Type.ime()) を使用します
- ステータスバー、ナビゲーションバー両方に重なる部分をずらす
  - windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) を使用します

#### Jetpack Compose

- ステータスバーに重なる部分をずらす
  - Modifier.statusBarsPadding() を使用します
- ナビゲーションバーに重なる部分をずらす
  - Modifier.navigationBarsPadding() を使用します
  - キーボードが表示される場合、UI が隠れてしまうため併せて Modifier.imePadding() を使用します
- ステータスバー、ナビゲーションバー両方に重なる部分をずらす
  - Modifier.systemBarsPadding() を使用します

ずらすかどうかについては、1 で実装した全画面描画を有効にした状態で動作確認しながら都度判断してください。

動作確認の観点としては、以下を確認するようにしてください。

- 全画面描画を有効にした状態で、ボタンやリストなどのタップ可能な領域がステータスバーやナビゲーションバーに被って押しづらくならないこと
  - ボトムバーがある場合、ナビゲーションバーを透過させない
  - ボトムバーがない場合、ナビゲーションバーを透過させ、一番下のアイテムが見えるまでスクロールできるようにする

##### ボトムバーがある例






|  ステータスバー、ナビゲーションバーに被っている例  |  修正後  |
| --- | --- |
|  ![bottom_bar_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/70c0b908-8dcc-7bbe-6de2-5a597bbc7bed.png)  |  ![bottom_bar_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/5b10b0b6-0042-33a7-0370-5f73a25e5cc3.png)  |

##### ボトムバーがない例

|  ステータスバー、ナビゲーションバーに被っている例  |  修正後  |
| --- | --- |
|  ![none_bottom_bar_before.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/af37de98-c885-7e28-6d37-6cfaf703c154.gif)  |  ![none_bottom_bar_after.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/55e1f4a7-0d3b-c242-60f2-74c80d4afe65.gif)  |

## つまずいたポイント

### Android 10 未満の端末で表示が崩れた

ジェスチャー ナビゲーションが導入されたのは API レベル 29 (Android 10) なので、Android 10 未満の端末で表示が崩れてしまいました。
以下の記事を参考に Android 10 未満の端末でも対応できるようにするか検討しましたが、今はまだ未対応です。

- [Android Developers Japan Blog: 古い端末でも Edge-to-edge にしたい](https://android-developers-jp.googleblog.com/2023/01/is-your-app-providing-a-backward-compatible-edge-to-edge-experience.html)

### ナビゲーションバーの色を変更したい

Jetpack Compose を導入している場合、基本的には Theme.kt にナビゲーションバーの色などを共通で対応しています。
ですが一部 Fragment で反映されない画面があり、その場合は Fragment 内で色変更の処理を入れます。

```kotlin
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    ...
    // ナビゲーションバーの背景色を白にする
    activity?.window?.navigationBarColor = Color.WHITE
    ...
}
...
override fun onDestroyView() {
    // ナビゲーションバーの背景色を戻す
    activity?.window?.navigationBarColor = Color.TRANSPARENT
    super.onDestroyView()
}
```

## まとめ

本記事では、[N予備校 Android](https://play.google.com/store/apps/details?id=nico.ed.nnn.zane&hl=ja&gl=US&pli=1)チームにて、コード例を交えてどのように [Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) を導入したかの手順、導入にあたってつまずいたポイントについてまとめました。
[Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) は画面によって対応方法が異なるため、リリース時は全画面および各 OS での動作確認をきちんと行っておく必要があり、実装よりも動作確認の方が大変な印象でした。
そのため画面の多い既存アプリへの導入はハードルが高いと感じましたが、全てのアプリで導入するべき機能でもあるため、導入できればしたほうが良いです。
[Edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) を導入する際の助けになれば幸いです。