فهم حواف النوافذ في WebView

يدير WebView محاذاة المحتوى باستخدام إطارَي عرض: إطار عرض التنسيق (حجم الصفحة) وإطار العرض المرئي (جزء الصفحة الذي يراه المستخدم فعليًا). مع أنّ إطار عرض التصميم ثابت بشكل عام، يتغيّر إطار العرض المرئي بشكل ديناميكي عندما يكبّر المستخدمون أو يصغّرون أو يتنقّلون أو عندما تظهر عناصر واجهة مستخدم النظام (مثل لوحة مفاتيح افتراضية).

توافق الميزات

تطوّر دعم WebView لعمليات إدراج النوافذ بمرور الوقت بهدف مواءمة سلوك محتوى الويب مع توقعات تطبيقات Android الأصلية، وذلك على النحو التالي:

الإجراء تمّت إضافة الميزة. النطاق
M136 displayCutout() وsystemBars() من خلال safe-area-insets في CSS تتوفّر فقط في WebViews بملء الشاشة.
M139 ime() (محرّر أسلوب الإدخال، وهو لوحة مفاتيح) من خلال تغيير حجم إطار العرض المرئي. جميع عناصر WebView
M144 displayCutout() وsystemBars() جميع عناصر WebView (بغض النظر عن حالة ملء الشاشة)

لمزيد من المعلومات، يُرجى الاطّلاع على WindowInsetsCompat.

آليات اللعب الأساسية

تتعامل WebView مع الحواف الداخلية من خلال آليتَين أساسيتَين:

  • المناطق الآمنة (displayCutout وsystemBars): يعيد WebView توجيه هذه الأبعاد إلى محتوى الويب من خلال متغيرات CSS safe-area-inset-‎*. يتيح ذلك للمطوّرين منع حجب عناصرهم التفاعلية (مثل أشرطة التنقّل) بسبب النتوءات أو أشرطة الحالة.

  • تغيير حجم إطار العرض المرئي باستخدام محرّر أسلوب الإدخال (IME): اعتبارًا من الإصدار 139 من Chrome، يغيّر محرّر أسلوب الإدخال (IME) حجم إطار العرض المرئي مباشرةً. تستند آلية تغيير الحجم هذه أيضًا إلى تقاطع WebView-Window. على سبيل المثال، في وضع تعدد المهام على Android، إذا كان الجزء السفلي من WebView يمتد بمقدار 200 وحدة بكسل مستقلة عن الكثافة (dp) أسفل الجزء السفلي من النافذة، سيكون إطار العرض المرئي أصغر بمقدار 200 وحدة بكسل مستقلة عن الكثافة (dp) من حجم WebView. لا يتم تطبيق تغيير حجم إطار العرض المرئي هذا (لكلّ من IME وتقاطع WebView مع النافذة) إلا على أسفل WebView. لا تتيح هذه الآلية تغيير الحجم عند التداخل من اليسار أو اليمين أو الأعلى. وهذا يعني أنّ لوحات المفاتيح المثبّتة التي تظهر على تلك الحواف لا تؤدي إلى تغيير حجم إطار العرض المرئي.

في السابق، كان إطار العرض المرئي يظل ثابتًا، ما كان يؤدي غالبًا إلى إخفاء حقول الإدخال خلف لوحة المفاتيح. من خلال تغيير حجم إطار العرض، يصبح الجزء المرئي من الصفحة قابلاً للتمرير تلقائيًا، ما يضمن وصول المستخدمين إلى المحتوى المخفي.

الحدود ومنطق التداخل

يجب أن تتلقّى WebView قيم إزاحة غير صفرية فقط عندما تتداخل عناصر واجهة مستخدم النظام (الأشرطة أو الفتحات في الشاشة أو لوحة المفاتيح) بشكل مباشر مع حدود شاشة WebView. إذا لم يتداخل WebView مع عناصر واجهة المستخدم هذه (على سبيل المثال، إذا كان WebView في منتصف الشاشة ولا يلامس أشرطة النظام)، يجب أن يتلقّى هذه الإزاحات كقيمة صفر.

لتجاوز هذا المنطق التلقائي وتزويد محتوى الويب بأبعاد النظام الكاملة بغض النظر عن التداخل، استخدِم طريقة setOnApplyWindowInsetsListener وأرجِع عنصر windowInsets الأصلي غير المعدَّل من أداة المعالجة. يمكن أن يساعد توفير أبعاد النظام الكاملة في ضمان اتساق التصميم من خلال السماح بمحاذاة محتوى الويب مع أجهزة الجهاز بغض النظر عن الموضع الحالي لمكوّن WebView. ويضمن ذلك انتقالاً سلسًا أثناء تحرّك WebView أو توسيعه ليلامس حواف الشاشة.

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;
});

إدارة أحداث تغيير الحجم

بما أنّ ظهور لوحة المفاتيح يؤدي الآن إلى تغيير حجم إطار العرض المرئي، قد يلاحظ رمز الويب أحداثًا متكررة لتغيير الحجم. على المطوّرين التأكّد من أنّ الرمز البرمجي لا يتفاعل مع أحداث تغيير الحجم هذه من خلال إزالة تركيز العنصر. يؤدي ذلك إلى إنشاء تكرار من فقدان التركيز وإغلاق لوحة المفاتيح، ما يمنع بيانات أدخلها المستخدم:

  1. يركّز المستخدم على عنصر إدخال.
  2. تظهر لوحة المفاتيح، ما يؤدي إلى بدء حدث تغيير الحجم.
  3. يمحو رمز الموقع الإلكتروني التركيز استجابةً لتغيير الحجم.
  4. يتم إخفاء لوحة المفاتيح لأنّ التركيز قد تم فقده.

للتخفيف من حدة هذا السلوك، راجِع أدوات الاستماع على الويب للتأكّد من أنّ تغييرات إطار العرض لا تؤدي عن غير قصد إلى تشغيل وظيفة blur() JavaScript أو سلوكيات محو التركيز.

تنفيذ معالجة الحواف الداخلية

تعمل إعدادات WebView التلقائية تلقائيًا مع معظم التطبيقات. ومع ذلك، إذا كان تطبيقك يستخدم تنسيقات مخصّصة (على سبيل المثال، إذا أضفت مساحة متروكة خاصة بك لاحتساب شريط الحالة أو لوحة المفاتيح)، يمكنك استخدام الطرق التالية لتحسين طريقة عمل محتوى الويب وواجهة المستخدم الأصلية معًا. إذا كانت واجهة المستخدم الأصلية تضيف مساحة متروكة إلى حاوية استنادًا إلى WindowInsets، عليك إدارة هذه المساحات المتروكة بشكل صحيح قبل وصولها إلى WebView لتجنُّب إضافة مساحة متروكة مضاعفة.

يشير مصطلح "الحشو المزدوج" إلى الحالة التي يطبّق فيها التنسيق الأصلي ومحتوى الويب أبعاد المسافة البادئة نفسها، ما يؤدي إلى مساحة غير ضرورية. على سبيل المثال، تخيَّل هاتفًا يحتوي على شريط حالة يبلغ عرضه 40 بكسل. يظهر الحاشية الداخلية التي تبلغ 40 بكسل في كل من العرض الأصلي وWebView. يضيف كل منهما مساحة متروكة بحجم 40 بكسل، ما يؤدي إلى ظهور فجوة بحجم 80 بكسل في أعلى الصفحة.

نهج التصفير

لمنع إضافة مساحة متروكة مضاعفة، عليك التأكّد من أنّه بعد استخدام عرض أصلي لبُعد مساحة داخلية من أجل إضافة مساحة متروكة، عليك إعادة ضبط هذا البُعد على صفر باستخدام Insets.NONE في عنصر WindowInsets جديد قبل تمرير العنصر المعدَّل إلى أسفل هيكلية طرق العرض إلى WebView.

عند تطبيق مساحة متروكة على طريقة عرض رئيسية، يجب بشكل عام استخدام طريقة التصفير من خلال ضبط Insets.NONE بدلاً من WindowInsetsCompat.CONSUMED. قد يعمل الزر WindowInsetsCompat.CONSUMED في حالات معيّنة. ومع ذلك، يمكن أن تحدث مشاكل إذا غيّر معالج تطبيقك الحواف الداخلية أو أضاف مساحة متروكة خاصة به. لا يتضمّن نهج التصفير هذه القيود.

تجنُّب المساحة الفارغة غير المرئية من خلال ضبط قيمة الهوامش الداخلية على صفر

إذا كنت تستخدم الحواف الداخلية عندما يكون التطبيق قد مرّر سابقًا حوافًا داخلية غير مستخدَمة، أو إذا تغيرت الحواف الداخلية (مثل إخفاء لوحة المفاتيح)، سيؤدي استخدامها إلى منع WebView من تلقّي إشعار التحديث اللازم. ويمكن أن يؤدي ذلك إلى احتفاظ WebView بمساحة متروكة غير مرئية من حالة سابقة (على سبيل المثال، الاحتفاظ بمساحة متروكة للوحة المفاتيح بعد إخفائها).

يوضّح المثال التالي تفاعلاً غير سليم بين التطبيق وWebView:

  1. الحالة الأولية: يمرّر التطبيق في البداية هوامش داخلية غير مستهلكة (على سبيل المثال، displayCutout() أو systemBars()) إلى WebView، الذي يضيف داخليًا مسافة بادئة إلى محتوى الويب.
  2. تغيير الحالة والخطأ: إذا غيّر التطبيق الحالة (على سبيل المثال، تم إخفاء لوحة المفاتيح) واختار التطبيق معالجة الحواف الناتجة من خلال عرض WindowInsetsCompat.CONSUMED.
  3. تم حظر الإشعار: يؤدي استهلاك المساحات الداخلية إلى منع نظام التشغيل Android من إرسال إشعار التحديث اللازم إلى أسفل هيكلية طرق عرض WebView.
  4. المساحة الفارغة: بما أنّ WebView لا يتلقّى التحديث، يحتفظ بالمساحة الفارغة من الحالة السابقة، ما يؤدي إلى ظهور مساحة فارغة (على سبيل المثال، الاحتفاظ بالمساحة الفارغة الخاصة بلوحة المفاتيح بعد إخفائها).

بدلاً من ذلك، استخدِم WindowInsetsCompat.Builder لضبط الأنواع التي يتم التعامل معها على صفر قبل تمرير العنصر إلى طرق العرض الفرعية. يُعلم ذلك WebView بأنّه تمّت مراعاة هذه المساحات الداخلية المحدّدة عند تفعيل الإشعار لمواصلة النزول إلى أسفل هيكلية طرق العرض.

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();
});

كيفية الإيقاف

لإيقاف هذه السلوكيات الحديثة والعودة إلى طريقة العرض القديمة، اتّبِع الخطوات التالية:

  1. عمليات اعتراض الحواف الداخلية: استخدِم setOnApplyWindowInsetsListener أو تجاوز onApplyWindowInsets في فئة فرعية من WebView.

  2. Clear insets: تعرض هذه السمة مجموعة مستخدَمة من الحواف الداخلية (على سبيل المثال، WindowInsetsCompat.CONSUMED) من البداية. يمنع هذا الإجراء إرسال الإشعار المضمّن إلى WebView بشكل كامل، ما يؤدي إلى إيقاف ميزة تغيير حجم إطار العرض الحديثة وإجبار WebView على الاحتفاظ بحجم إطار العرض المرئي الأولي.