הסבר על חלונות מוטמעים ב-WebView

‫WebView מנהל את יישור התוכן באמצעות שני אזורי תצוגה: אזור התצוגה של הפריסה (גודל הדף) ואזור התצוגה החזותי (החלק בדף שהמשתמש רואה בפועל). אזור התצוגה בפריסה הוא בדרך כלל סטטי, אבל אזור התצוגה החזותי משתנה באופן דינמי כשהמשתמשים מבצעים זום, גלילה או כשמופיעים רכיבי ממשק משתמש של המערכת (כמו המקלדת הווירטואלית).

תאימות התכונות

התמיכה ב-WebView בהזחות של חלונות התפתחה עם הזמן כדי להתאים את התנהגות התוכן באינטרנט לציפיות של אפליקציות מקוריות ל-Android:

Milestone התכונה נוספה היקף
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 מתרחב ב-200dp מתחת לחלק התחתון של החלון, אז אזור התצוגה החזותי קטן ב-200dp מהגודל של 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. המקלדת מוסתרת כי המיקוד אבד.

כדי למנוע את הבעיה הזו, כדאי לבדוק את מאזיני הצד של האינטרנט כדי לוודא ששינויים באזור התצוגה לא מפעילים בטעות את פונקציית ה-JavaScript של blur() או התנהגויות של ניקוי המיקוד.

הטמעה של טיפול ב-inset

הגדרות ברירת המחדל של WebView פועלות באופן אוטומטי ברוב האפליקציות. עם זאת, אם האפליקציה שלכם משתמשת בפריסות בהתאמה אישית (לדוגמה, אם הוספתם ריווח משלכם כדי להתחשב בסרגל המצב או במקלדת), אתם יכולים להשתמש בגישות הבאות כדי לשפר את האופן שבו תוכן האינטרנט וממשק המשתמש המקורי פועלים יחד. אם ממשק המשתמש המקורי שלכם מוסיף ריווח פנימי (padding) למאגר על סמך WindowInsets, אתם צריכים לנהל את השוליים הפנימיים האלה בצורה נכונה לפני שהם מגיעים ל-WebView, כדי למנוע ריווח פנימי כפול.

ריווח כפול הוא מצב שבו הפריסה המקורית ותוכן האינטרנט משתמשים באותם ממדי שוליים פנימיים, וכתוצאה מכך יש ריווח מיותר. לדוגמה, נניח שיש טלפון עם שורת סטטוס בגובה 40px. גם התצוגה המקורית וגם ה-WebView רואים את השוליים הפנימיים של 40px. שתי הפקודות מוסיפות 40px של ריווח פנימי, כך שהמשתמש יראה רווח של 80px בחלק העליון.

גישת האיפוס

כדי למנוע ריווח כפול, צריך לוודא שאחרי שתצוגה מקורית משתמשת במאפיין inset dimension לריווח, מאפסים את המאפיין הזה לאפס באמצעות Insets.NONE באובייקט WindowInsets חדש, לפני שמעבירים את האובייקט ששונה בהיררכיית התצוגה אל WebView.

כשמחילים ריווח פנימי על תצוגת אב, בדרך כלל כדאי להשתמש בגישת האיפוס על ידי הגדרת Insets.NONE במקום WindowInsetsCompat.CONSUMED. יכול להיות שהחזרה WindowInsetsCompat.CONSUMED תפעל במצבים מסוימים. עם זאת, יכולות להיות בעיות אם המטפל של האפליקציה משנה את השוליים הפנימיים או מוסיף ריווח משלו. לגישת האיפוס אין את המגבלות האלה.

איך להימנע מריווח רפאים על ידי איפוס של שוליים פנימיים

אם משתמשים ב-insets כשהאפליקציה העבירה בעבר insets שלא נעשה בהם שימוש, או אם ה-insets משתנים (למשל, המקלדת מוסתרת), השימוש בהם מונע מ-WebView לקבל את עדכון ההתראה הדרוש. הדבר עלול לגרום ל-WebView לשמור על ריווח רפאים ממצב קודם (לדוגמה, שמירת ריווח המקלדת אחרי שהמקלדת מוסתרת).

בדוגמה הבאה מוצגת אינטראקציה פגומה בין האפליקציה לבין WebView:

  1. מצב התחלתי: האפליקציה מעבירה בהתחלה שוליים פנימיים שלא נצרכו (לדוגמה, displayCutout() או systemBars()) אל WebView, שמחיל באופן פנימי ריווח על תוכן האינטרנט.
  2. שינוי מצב ושגיאה: אם האפליקציה משנה מצב (לדוגמה, המקלדת מוסתרת) והאפליקציה בוחרת לטפל בשוליים שנוצרו על ידי החזרת WindowInsetsCompat.CONSUMED.
  3. ההתראה נחסמה: השימוש ב-insets מונע ממערכת 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 או ב-override של onApplyWindowInsets במחלקת משנה של WebView.

  2. Clear insets: מחזירה קבוצה של שוליים פנימיים (לדוגמה, WindowInsetsCompat.CONSUMED) מההתחלה. הפעולה הזו מונעת את ההפצה של ההודעה על השוליים הפנימיים אל WebView, ובפועל משביתה את שינוי הגודל של אזור התצוגה המודרני ומאלצת את WebView לשמור על הגודל החזותי הראשוני של אזור התצוגה.