Memahami inset jendela di WebView

WebView mengelola perataan konten menggunakan dua area pandang: area pandang tata letak (ukuran halaman) dan area pandang visual (bagian halaman yang sebenarnya dilihat pengguna). Meskipun area pandang tata letak umumnya statis, area pandang visual berubah secara dinamis saat pengguna melakukan zoom, men-scroll, atau saat elemen UI sistem (seperti keyboard software) muncul.

Kompatibilitas fitur

Dukungan WebView untuk inset jendela telah berkembang dari waktu ke waktu untuk menyelaraskan perilaku konten web dengan ekspektasi aplikasi Android native:

Milestone Fitur ditambahkan Cakupan
M136 displayCutout() dan systemBars() melalui CSS safe-area-insets. Hanya WebView layar penuh.
M139 ime() (editor metode input, yaitu keyboard) melalui pengubahan ukuran area pandang visual. Semua WebView.
M144 Dukungan displayCutout() dan systemBars(). Semua WebView (terlepas dari status layar penuh).

Untuk mengetahui informasi selengkapnya, lihat WindowInsetsCompat.

Mekanisme inti

WebView menangani inset melalui dua mekanisme utama:

  • Area aman (displayCutout, systemBars): WebView meneruskan dimensi ini ke konten web melalui variabel CSS safe-area-inset-*. Hal ini memungkinkan developer mencegah elemen interaktif mereka sendiri (seperti panel navigasi) terhalang oleh poni atau status bar.

  • Pengubahan ukuran area tampilan visual menggunakan editor metode input (IME): Mulai M139, editor metode input (IME) secara langsung mengubah ukuran area tampilan visual. Mekanisme pengubahan ukuran ini juga didasarkan pada persimpangan WebView-Window. Misalnya, dalam mode multitasking Android, jika bagian bawah WebView meluas 200dp di bawah bagian bawah jendela, viewport visual berukuran 200dp lebih kecil daripada ukuran WebView. Pengubahan ukuran area pandang visual ini (untuk IME dan persimpangan WebView-Window) hanya diterapkan di bagian bawah WebView. Mekanisme ini tidak mendukung pengubahan ukuran untuk tumpang-tindih kiri, kanan, atau atas. Artinya, keyboard yang terpasang di tepi tersebut tidak memicu pengubahan ukuran area pandang visual.

Sebelumnya, area pandang visual tetap tetap, sering kali menyembunyikan kolom input di balik keyboard. Dengan mengubah ukuran viewport, bagian halaman yang terlihat akan dapat di-scroll secara default, sehingga pengguna dapat mengakses konten yang terhalang.

Logika batas dan tumpang-tindih

WebView hanya boleh menerima nilai inset non-nol saat elemen UI sistem (panel, potongan layar, atau keyboard) tumpang-tindih langsung dengan batas layar WebView. Jika WebView tidak tumpang-tindih dengan elemen UI ini (misalnya, jika WebView dipusatkan di layar dan tidak menyentuh kolom sistem), WebView harus menerima inset tersebut sebagai nol.

Untuk mengganti logika default ini dan memberikan dimensi sistem lengkap pada konten web terlepas dari tumpang-tindih, gunakan metode setOnApplyWindowInsetsListener dan tampilkan objek windowInsets asli yang tidak dimodifikasi dari pemroses. Menyediakan dimensi sistem yang lengkap dapat membantu memastikan konsistensi desain dengan memungkinkan konten web selaras dengan hardware perangkat, terlepas dari posisi WebView saat ini. Hal ini memastikan transisi yang lancar saat WebView berpindah atau diperluas untuk menyentuh tepi layar.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets ->
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    windowInsets
}

Java

ViewCompat.setOnApplyWindowInsetsListener(myWebView, (v, windowInsets) -> {
  // By returning the original windowInsets object, we override the default
  // behavior that zeroes out system insets (like system bars or display
  // cutouts) when they don't directly overlap the WebView's screen bounds.
  return windowInsets;
});

Mengelola peristiwa pengubahan ukuran

Karena visibilitas keyboard kini memicu pengubahan ukuran area pandang visual, kode web mungkin melihat peristiwa pengubahan ukuran yang lebih sering. Developer harus memastikan kode mereka tidak bereaksi terhadap peristiwa pengubahan ukuran ini dengan menghapus fokus elemen. Tindakan ini akan membuat loop kehilangan fokus dan penutupan keyboard yang mencegah input pengguna:

  1. Pengguna berfokus pada elemen input.
  2. Keyboard muncul, memicu peristiwa pengubahan ukuran.
  3. Kode situs menghapus fokus sebagai respons terhadap pengubahan ukuran.
  4. Keyboard disembunyikan karena fokus hilang.

Untuk mengurangi perilaku ini, tinjau pemroses sisi web untuk memastikan perubahan area tampilan tidak secara tidak sengaja memicu fungsi JavaScript blur() atau perilaku penghapusan fokus.

Menerapkan penanganan inset

Setelan default WebView berfungsi secara otomatis untuk sebagian besar aplikasi. Namun, jika aplikasi Anda menggunakan tata letak kustom (misalnya, jika Anda menambahkan padding sendiri untuk memperhitungkan status bar atau keyboard), Anda dapat menggunakan pendekatan berikut untuk meningkatkan cara kerja bersama konten web dan UI native. Jika UI native Anda menerapkan padding ke penampung berdasarkan WindowInsets, Anda harus mengelola inset ini dengan benar sebelum mencapai WebView untuk menghindari padding ganda.

Pengisihan ganda adalah situasi saat tata letak native dan konten web menerapkan dimensi inset yang sama, sehingga menghasilkan penspasian yang berlebihan. Misalnya, bayangkan ponsel dengan status bar 40 px. Tampilan native dan WebView melihat inset 40 px. Keduanya menambahkan padding 40 px, sehingga pengguna melihat celah 80 px di bagian atas.

Pendekatan Zeroing

Untuk mencegah padding ganda, Anda harus memastikan bahwa setelah tampilan native menggunakan dimensi inset untuk padding, Anda mereset dimensi tersebut ke nol menggunakan Insets.NONE pada objek WindowInsets baru sebelum meneruskan objek yang diubah ke hierarki tampilan WebView.

Saat menerapkan padding ke tampilan induk, Anda umumnya harus menggunakan pendekatan pengurangan dengan menyetel Insets.NONE, bukan WindowInsetsCompat.CONSUMED. Mengembalikan WindowInsetsCompat.CONSUMED mungkin berfungsi dalam situasi tertentu. Namun, masalah dapat terjadi jika handler aplikasi Anda mengubah inset atau menambahkan padding-nya sendiri. Pendekatan peniadaan tidak memiliki batasan ini.

Menghindari padding hantu dengan menyetel inset ke nol

Jika Anda menggunakan inset saat aplikasi sebelumnya telah meneruskan inset yang tidak digunakan, atau jika inset berubah (seperti keyboard yang disembunyikan), menggunakan inset akan mencegah WebView menerima notifikasi update yang diperlukan. Hal ini dapat menyebabkan WebView mempertahankan padding hantu dari status sebelumnya (misalnya, mempertahankan padding keyboard setelah keyboard disembunyikan).

Contoh berikut menunjukkan interaksi yang terganggu antara aplikasi dan WebView:

  1. Status awal: Aplikasi awalnya meneruskan inset yang tidak digunakan (misalnya, displayCutout() atau systemBars()) ke WebView, yang secara internal menerapkan padding ke konten web.
  2. Perubahan status dan error: Jika aplikasi mengubah status (misalnya, keyboard disembunyikan) dan aplikasi memilih untuk menangani inset yang dihasilkan dengan menampilkan WindowInsetsCompat.CONSUMED.
  3. Notifikasi diblokir: Menggunakan inset mencegah sistem Android mengirim notifikasi update yang diperlukan ke hierarki tampilan hingga ke WebView.
  4. Padding hantu: Karena WebView tidak menerima update, WebView mempertahankan padding dari status sebelumnya, sehingga menyebabkan padding hantu (misalnya, mempertahankan padding keyboard setelah keyboard disembunyikan).

Sebagai gantinya, gunakan WindowInsetsCompat.Builder untuk menyetel jenis yang ditangani ke nol sebelum meneruskan objek ke tampilan turunan. Hal ini memberi tahu WebView bahwa inset tertentu tersebut telah diperhitungkan saat mengaktifkan notifikasi untuk terus turun dalam hierarki tampilan.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets ->
    // 1. Identify the inset types you want to handle natively
    val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()

    // 2. Extract the dimensions and apply them as padding to the native container
    val insets = windowInsets.getInsets(types)
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom)

    // 3. Return a new WindowInsets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal state.
    WindowInsetsCompat.Builder(windowInsets)
        .setInsets(types, Insets.NONE)
        .build()
}

Java

ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> {
  // 1. Identify the inset types you want to handle natively
  int types = WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout();

  // 2. Extract the dimensions and apply them as padding to the native container
  Insets insets = windowInsets.getInsets(types);
  rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom);

  // 3. Return a new Insets object with the handled types set to NONE (zeroed).
  // This informs the WebView that these areas are already padded, preventing
  // double-padding while still allowing the WebView to update its internal
  // state.
  return new WindowInsetsCompat.Builder(windowInsets)
    .setInsets(types, Insets.NONE)
    .build();
});

Cara menyisih

Untuk menonaktifkan perilaku modern ini dan kembali ke penanganan area tampilan lama, lakukan tindakan berikut:

  1. Inset pencegat: Gunakan setOnApplyWindowInsetsListener atau ganti onApplyWindowInsets di subclass WebView.

  2. Inset yang jelas: Menampilkan set inset yang digunakan (misalnya, WindowInsetsCompat.CONSUMED) dari awal. Tindakan ini mencegah notifikasi inset berpropagasi ke WebView sepenuhnya, sehingga secara efektif menonaktifkan pengubahan ukuran viewport modern dan memaksa WebView mempertahankan ukuran viewport visual awalnya.