4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アプリの対象 API レベル 35 で初めて edge-to-edge に対処する[Android View編]

Last updated at Posted at 2024-07-21

Android 15 の動作変更点の一つである Edge-to-edge enforcement を読みましたか? アプリの対象 API レベル 35 に上げると edge-to-edge が強制適用されるようだ。これまで edge-to-edge を無視して来てしまい、既存アプリに edge-to-edge が適用されたらどうなってしまうのかわからない、そしてどう対処すれば良いのか悩んでいる人に読んでほしい。

edge-to-edgeとは

まずは edge-to-edge が何かを知る必要がある。

edge-to-edge とは以下の仕様が適用されている状態のこと。

  • 3ボタンナビゲーションバーが半透明
  • ジェスチャーナビゲーションバーが透明
  • ステータスバーが透明
  • コンテンツは、インセットかパディングが適用されない限り、ナビゲーション バー、ステータスバー、キャプション バーなどのシステムバーの背後に描画される

💡 インセットって何?
画面の端に配置されたシステムのUI(ステータスバーやナビゲーションバーなど) に隠れてしまう領域のこと。

上記は公式ドキュメントの文章ほぼそのままだが、わかるようでわからないだろう。 次の章で edge-to-edge が有効のケースと無効のケースを比較することで edge-to-edge が何かが見えてくるはず。

edge-to-edge が有効になるケースは?

Android 端末、API レベル、enableEdgeToEdge() の使用・未使用の組み合わせによって、アプリが edge-to-edge(画面端までコンテンツを表示するデザイン)になるかどうかを検証してみる。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge() // API 34 以下で edge-to-edge にする場合
        setContentView(R.layout.activity_main)
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e1f6cf"
    tools:context=".MainActivity">
    
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Hello Android!"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

  </androidx.constraintlayout.widget.ConstraintLayout>
themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
  <!-- Base application theme. -->
  <style name="Base.Theme.EdgeXml" parent="Theme.Material3.DayNight.NoActionBar">
      <!-- Customize your light theme here. -->
      <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
  </style>

  <style name="Theme.EdgeXml" parent="Base.Theme.EdgeXml" />
</resources>

Android 14 端末(Pixel8)

Android 14 端末は enableEdgeToEdge() が有効の場合のみ edge-to-edge が有効になる。

  • API 34 + enableEdgeToEdge() 無効

  • API 34 + enableEdgeToEdge() 有効

    edge-to-edge が有効。

  • API 35 + enableEdgeToEdge() 無効

  • API 35 + enableEdgeToEdge() 有効

    edge-to-edge が有効。

Android 15 端末(Pixel8)

Android 15 端末は enableEdgeToEdge() が有効の場合に加え、API 35 の場合に edge-to-edge が有効となる。

  • API 34 + enableEdgeToEdge() 未使用

    ステータスバー領域にカメラ領域が含まれていないので、 Android 14 よりも狭い。

  • API 34 + enableEdgeToEdge() 使用

    edge-to-edge が有効。ただ、コンテンツとステータスバーが被っているが Android 14 端末と微妙に異なる。

  • API 35 + enableEdgeToEdge() 未使用

    edge-to-edge が有効。ただ、コンテンツとステータスバーが被っているが Android 14 端末と微妙に異なる。また、ステータスバーのアイコンが白になるのも気になる。

  • API 35 + enableEdgeToEdge() 使用

    edge-to-edge が有効。ただ、コンテンツとステータスバーが被っているが Android 14 端末と微妙に異なる。

ターゲット API 35 にアップデートすることによる影響

ターゲット API が 34 以下の既存アプリにおいて edge-to-edge を意識していないもしくは edge-to-edge にしたくない状態、つまりenableEdgeToEdge() 未使用状態の場合、ターゲット API を 35 にアップデートするとAndroid 15 端末で edge-to-edge になってしまう。

💡 enableEdgeToEdge() は androidx.activity:activity- ktx:1.5.0-alpha01 から使用可能。1.5.0-alpha01 は Jan 26, 2022 にリリースされている。

💡 enableEdgeToEdge() を使用せずに edge-to-edge にする方法は Manually set up the edge-to-edge display を参照。

edge-to-edge 有効時にステータスバー・ナビゲーションバーとコンテンツが被らないようにする

公式ドキュメント インセットを使用して重なりを処理する の「システムバーのインセット」について説明する。 公式ドキュメントは FAB という特殊な場合しか説明してくれないので、ここでは「ステータスバー・ナビゲーションバーとコンテンツを被らないようにするにはどうすれば良いのか?」という最も基本的なところを説明をしたい。

ステータスバー・ナビゲーションバーとコンテンツが被らないようにする。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge() // API 35 化時に Android 14 端末も edge-to-edge にする場合
        setContentView(R.layout.activity_main)
+       ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+           val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+           v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+           insets
+       }
    }
}

activity_main.xml の Top レベルに配置した ConstraintLayout はステータスバー領域に表示されるが、その子ビューは ステータスバーに被らないように表示される。
ページの一番下に <TextView> を配置し、ナビゲーションバー領域はどのように表示されるのを確認してみる。

xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
...      
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Android!"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

  </androidx.constraintlayout.widget.ConstraintLayout>

【課題】 edge-to-edge 対処は ステータスバー・ナビゲーションバーとコンテンツが被らないようにすればいいだけではない?

公式ドキュメント ディスプレイ カットアウト インセットシステム ジェスチャー インセットの記載があり、ステータスバー・ナビゲーションバーとコンテンツが被らないようにするだけで edge-to-edge 対処が完了するわけではない。例を以下に示す。

3ボタンナビゲーションの背景が半透明

ステータスバー・ナビゲーションバーの背景を設定した状態で、ナビゲーションモードが3ボタンナビゲーションの場合とジェスチャーナビゲーションの場合で比較してみる。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e1f6cf" //ステータスバー・ナビゲーションバーを含めた全背景
    tools:context=".MainActivity">
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Android!"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

3ボタンナビゲーションの背景が半透明になっているのがお分かりだろうか。半透明を解消し、指定した通りの背景にするには window.isNavigationBarContrastEnforced プロパティを false にする。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge() // API 35 化時に Android 14 端末も edge-to-edge にする場合
        setContentView(R.layout.activity_android_view)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

+       // 3ボタンナビゲーションの背景の半透明を無効化する
+       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+           window.isNavigationBarContrastEnforced = false
+       }
    }
}

3ボタンナビゲーションの背景が半透明になる事象が解消された。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?