הגדרת חלונות מוטמעים

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

  1. מטרגטים ל-Android 15 (רמת API ‏35) ואילך כדי לאכוף תצוגה מקצה לקצה ב-Android 15 ואילך. האפליקציה מוצגת מאחורי ממשק המשתמש של המערכת. אתם יכולים לשנות את ממשק המשתמש של האפליקציה באמצעות רכיבי inset.
  2. אפשר גם להפעיל את enableEdgeToEdge() ב-Activity.onCreate(), כדי שהאפליקציה תהיה במסך מלא בגרסאות קודמות של Android.
  3. מגדירים את הערך android:windowSoftInputMode="adjustResize" בעמודה AndroidManifest.xml של הפעילות. ההגדרה הזו מאפשרת לאפליקציה לקבל את הגודל של IME התוכנה כרכיבים מוטמעים, וכך תוכלו להחיל את הפריסה והרווחים המתאימים כשה-IME מופיע ונעלם באפליקציה.

    <!-- In your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

שימוש ב-Compose APIs

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

לדוגמה, זוהי השיטה הבסיסית ביותר להוספת התמונות הקטנות לתוכן של כל האפליקציה:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

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

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

יש שתי דרכים עיקריות להשתמש בסוגי ההכנסה האלה כדי לשנות את הפריסות של Composable: מודפי ריפוד ומודפי שינוי גודל ההכנסה.

משתני Padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) מחילה את ה-insets של החלון הנתון כמילוי, בדיוק כמו Modifier.padding. לדוגמה, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) מחילה את הקטעים הפנימיים של הציור הבטוח כמילוי לכל 4 הצדדים.

יש גם כמה שיטות כלי מובנות לסוגי התצוגה הנפוצים ביותר. Modifier.safeDrawingPadding() הוא אחד מה-methods האלה, והוא שווה ערך ל-Modifier.windowInsetsPadding(WindowInsets.safeDrawing). יש משתני אופן פעולה דומים לסוגים האחרים של התצוגה הממוזערת.

גורמים לשינוי גודל התמונה הממוזערת

המשתנים הבאים מחילים כמות של חלונות מוטמעים על ידי הגדרת הגודל של הרכיב לגודל של החלונות המוטמעים:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

החלת הצד ההתחלתי של windowInsets כרוחב (כמו Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

החלת הצד הסופי של windowInsets כרוחב (כמו Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

החלון מוגדר לגובה של החלק העליון של windowInsets (כמו Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

החלת הצד התחתון של windowInsets כגובה (כמו Modifier.height)

המשתנים האלה שימושיים במיוחד לקביעת הגודל של Spacer שיתפוס את המרחב של התמונות הממוזערות:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

צריכת מודעות מוטמעות

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

בנוסף, אם כבר נוצלו הפריימים הקטנים, מודификаторי הגודל שלהם מונעים שימוש באותו קטע של פריימים קטנים יותר מפעם אחת. עם זאת, מכיוון שהן משנות את הגודל שלהן ישירות, הן לא צורכות רכיבי inset בעצמן.

כתוצאה מכך, משתני padding בתוך עץ משנים באופן אוטומטי את כמות ה-padding שחלה על כל רכיב.

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

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

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

כשה-IME נפתח, האנימציה של הרכיבים הפנימיים של ה-IME משתנה בהתאם לגודל של ה-IME, והמַגְדִּיל imePadding() מתחיל להחיל תוספת מרווח בתחתית המסך כדי לשנות את הגודל של LazyColumn בזמן פתיחת ה-IME. כשהמשתנה המשנה imePadding() מתחיל להחיל את החפיפה התחתונה, הוא מתחיל גם לצרוך את כמות החפיפות הזו. לכן, הגובה של Spacer מתחיל לרדת, כי חלק מהרווח בין פסי המערכת כבר הוחל על ידי המשתנה imePadding(). אחרי שהמַעֲדִיל imePadding() מחיל כמות של ריפוד בחלק התחתון שגדולה מהעמודות של המערכת, הגובה של Spacer הוא אפס.

כשה-IME נסגר, השינויים מתרחשים בכיוון ההפוך: Spacer מתחיל להתרחב מגובה אפס ברגע ש-imePadding() חל על פחות מהצד התחתון של סרחי המערכת, עד ש-Spacer תואם לגובה של הצד התחתון של סרחי המערכת ברגע שהאנימציה של ה-IME מסתיימת.

איור 2. עמודה עצלה מקצה לקצה עם TextField.

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

גם Modifier.consumeWindowInsets(insets: WindowInsets) צורך רכיבי inset באותו אופן כמו Modifier.windowInsetsPadding, אבל הוא לא מחיל את רכיבי ה-inset שנצרכו כמילוי. אפשר להשתמש באפשרות הזו בשילוב עם המשתנים לשינוי גודל ההכנסה, כדי לציין לשכנים של הרכיב שכבר נצרכו כמה הכנסות:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

התנהגות הפונקציה Modifier.consumeWindowInsets(paddingValues: PaddingValues) דומה מאוד לזו של הגרסה עם הארגומנט WindowInsets, אבל היא מקבלת לצריכה ערך PaddingValues שרירותי. הדבר שימושי כדי להודיע לצאצאים מתי הרווח או המילוי מסופקים על ידי מנגנון אחר מלבד המשתנים של המילוי הפנימי, כמו Modifier.padding רגיל או רווחים בגובה קבוע:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

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

שוליים ושלבי Jetpack פיתוח נייטיב

Compose משתמש בממשקי ה-API הבסיסיים של AndroidX כדי לעדכן את הרכיבים הפנימיים ולספק להם אנימציה, תוך שימוש בממשקי ה-API הבסיסיים של הפלטפורמה לניהול הרכיבים הפנימיים. בגלל התנהגות הפלטפורמה הזו, ל-insets יש קשר מיוחד לשלבים של Jetpack Compose.

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