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

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

תאימות לתכונות

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

Milestone התכונה נוספה היקף
M136 תמיכה ב-displayCutout() וב-systemBars() באמצעות safe-area-insets ב-CSS. תצוגות WebView במסך מלא בלבד.
M139 תמיכה ב-ime() (עורך שיטת קלט, שהוא מקלדת) באמצעות שינוי הגודל של אזור התצוגה החזותי. כל רכיבי ה-WebView.
M144 תמיכה ב-displayCutout() וב-systemBars(). כל רכיבי ה-WebView (ללא קשר למצב המסך המלא).

מידע נוסף זמין במאמר WindowInsetsCompat.

מכניקת הליבה

‫WebView מטפל בתוספות באמצעות שני מנגנונים עיקריים:

  • אזורים בטוחים (displayCutout, systemBars): WebView מעביר את המידות האלה לתוכן מהאינטרנט באמצעות משתני CSS safe-area-inset-‎*. כך המפתחים יכולים למנוע מצב שבו רכיבים אינטראקטיביים משלהם (כמו סרגלי ניווט) מוסתרים על ידי מגרעות או שורות סטטוס.

  • שינוי הגודל של אזור התצוגה החזותי באמצעות עורך שיטת הקלט (IME): החל מגרסה M139, עורך שיטת הקלט (IME) משנה ישירות את הגודל של אזור התצוגה החזותי. מנגנון שינוי הגודל הזה מבוסס גם על החיתוך בין WebView לחלון. לדוגמה, במצב ריבוי משימות ב-Android, אם החלק התחתון של WebView חורג ב-200dp מתחת לחלק התחתון של החלון, אזור התצוגה החזותי קטן ב-200dp מהגודל של WebView. שינוי הגודל של אזור התצוגה החזותי (גם ל-IME וגם לחיתוך של WebView-Window) חל רק על החלק התחתון של WebView. המנגנון הזה לא תומך בשינוי הגודל של חפיפה שמאלה, ימינה או למעלה. כלומר, מקלדות מעוגנות שמופיעות בקצוות האלה לא גורמות לשינוי הגודל של אזור התצוגה החזותי.

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

לוגיקת הגבולות והחפיפה

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

כדי לשנות את הלוגיקה הזו שמוגדרת כברירת מחדל ולספק לתוכן מהאינטרנט את המידות המלאות של המערכת, בלי קשר לחפיפה, משתמשים ב-method‏ 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 או את ההתנהגויות של ניקוי המיקוד.

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

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

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

גישת האיפוס

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

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

הימנעות מריפוד רפאים על ידי איפוס ה-inset

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

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

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

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