שימוש בסיווגים של גדלים של חלונות

כדאי לנסות את הכתיבה
‫Jetpack Compose היא ערכת הכלים המומלצת לבניית ממשק משתמש ל-Android. איך משתמשים בסיווגים של גודל החלון בכתיבה.

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

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

איור 1. ייצוגים של מחלקות גודל חלון שמבוססות על רוחב.
איור 2. ייצוגים של סיווגים של גודל חלון לפי גובה.

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

סיווג לפי גודל נקודת עצירה (breakpoint) ייצוג המכשיר
רוחב קומפקטי רוחב < 600dp ‫99.96% מהטלפונים במצב לאורך
רוחב בינוני ‫600dp ≤ width < 840dp ‫93.73% מהטאבלטים בפריסה לאורך,

רוב המסכים הפנימיים הגדולים במצב פתוח בפריסה לאורך

רוחב מורחב ‫840dp ≤ width < 1200dp ‫97.22% מהטאבלטים בפריסה לרוחב,

רוב המסכים הפנימיים הגדולים במצב פתוח ובפריסה לרוחב הם ברוחב מורחב של לפחות

רוחב גדול ‫1200dp ≤ width < 1600dp מסכים של טאבלטים גדולים
רוחב גדול במיוחד ‫width ≥ 1600dp תצוגות למחשב
גובה קומפקטי גובה < 480dp ‫99.78% מהטלפונים בפריסה לרוחב
גובה בינוני ‫480dp ≤ גובה < 900dp ‫96.56% מהטאבלטים במצב לרוחב,

‫97.59% מהטלפונים במצב לאורך

גובה מורחב גובה ≥ 900dp ‫94.25% מהטאבלטים במצב לאורך

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

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

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

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

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

אפשר לחשב את הערך הנוכחי WindowSizeClass באמצעות WindowSizeClass#compute() הפונקציה שסופקה על ידי Jetpack ספריית windowManager. הדוגמה הבאה מראה איך לחשב את סיווג גודל החלון ולקבל עדכונים בכל פעם שינויים במחלקות של גודל החלון:

Kotlin

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

        // ...

        // Replace with a known container that you can safely add a
        // view to where the view won't affect the layout and the view
        // won't be replaced.
        val container: ViewGroup = binding.container

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged(). This is required for all
        // activities, even those that don't handle configuration
        // changes. You can't use Activity.onConfigurationChanged(),
        // since there are situations where that won't be called when
        // the configuration changes. View.onConfigurationChanged() is
        // called in those scenarios.
        container.addView(object : View(this) {
            override fun onConfigurationChanged(newConfig: Configuration?) {
                super.onConfigurationChanged(newConfig)
                computeWindowSizeClasses()
            }
        })

        computeWindowSizeClasses()
    }

    private fun computeWindowSizeClasses() {
        val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
        val width = metrics.bounds.width()
        val height = metrics.bounds.height()
        val density = resources.displayMetrics.density
        val windowSizeClass = WindowSizeClass.compute(width/density, height/density)
        // COMPACT, MEDIUM, or EXPANDED
        val widthWindowSizeClass = windowSizeClass.windowWidthSizeClass
        // COMPACT, MEDIUM, or EXPANDED
        val heightWindowSizeClass = windowSizeClass.windowHeightSizeClass

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

Java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        // Replace with a known container that you can safely add a
        // view to where the view won't affect the layout and the view
        // won't be replaced.
        ViewGroup container = binding.container;

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged(). This is required for all
        // activities, even those that don't handle configuration
        // changes. You can't use Activity.onConfigurationChanged(),
        // since there are situations where that won't be called when
        // the configuration changes. View.onConfigurationChanged() is
        // called in those scenarios.
        container.addView(new View(this) {
            @Override
            protected void onConfigurationChanged(Configuration newConfig) {
                super.onConfigurationChanged(newConfig);
                computeWindowSizeClasses();
            }
        });

        computeWindowSizeClasses();
    }

    private void computeWindowSizeClasses() {
        WindowMetrics metrics = WindowMetricsCalculator.getOrCreate()
                .computeCurrentWindowMetrics(this);

        int width = metrics.getBounds().width();
        int height = metrics.getBounds().height();
        float density = getResources().getDisplayMetrics().density;
        WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
        // COMPACT, MEDIUM, or EXPANDED
        WindowWidthSizeClass widthWindowSizeClass = windowSizeClass.getWindowWidthSizeClass();
        // COMPACT, MEDIUM, or EXPANDED
        WindowHeightSizeClass heightWindowSizeClass = windowSizeClass.getWindowHeightSizeClass();

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

בדיקת סיווגים של גודל חלון

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

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

השלבים הבאים

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

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