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

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

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

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

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

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

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

يعالج WebView المساحات المتروكة من خلال آليتَين أساسيتَين:

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

  • تغيير حجم إطار العرض المرئي باستخدام محرِّر أسلوب الإدخال (IME): اعتبارًا من الإصدار M139، يغيّر محرِّر أسلوب الإدخال (IME) حجم إطار العرض المرئي مباشرةً. تستند آلية تغيير الحجم هذه أيضًا إلى تقاطع WebView-Window. على سبيل المثال، في وضع تعدد المهام في Android، إذا كان الجزء السفلي من WebView يمتد بمقدار 200 بكسل مستقل الكثافة أسفل النافذة، يكون إطار العرض المرئي أصغر بمقدار 200 بكسل مستقل الكثافة من حجم WebView. لا يتم تطبيق تغيير حجم إطار العرض المرئي هذا (لكل من IME وتقاطع WebView-Window) إلا على الجزء السفلي من 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. تختفي لوحة المفاتيح بسبب فقدان التركيز.

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

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

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

تحدث مشكلة "الحشو المزدوج" عندما يطبّق كل من التنسيق الأصلي ومحتوى على الويب أبعاد الإدراج نفسها، ما يؤدي إلى تكرار المسافات. على سبيل المثال، تخيل هاتفًا بشريط حالة 40 بكسل. يظهر كل من العرض الأصلي وWebView بمقدار 40 بكسل. يضيف كلاهما 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. محو العناصر المضمّنة: يمكنك إعادة مجموعة من العناصر المضمّنة التي تم استخدامها (مثل WindowInsetsCompat.CONSUMED) من البداية. يمنع هذا الإجراء انتشار إشعار الإدراج إلى WebView تمامًا، ما يؤدي إلى إيقاف تغيير حجم إطار العرض الحديث بشكل فعّال وإجبار WebView على الاحتفاظ بحجم إطار العرض المرئي الأولي.