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

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

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

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

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

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

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

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

רוחב מורחב רוחב ≥ 840dp 97.22% מהטאבלטים בפריסה לרוחב,

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

גובה קומפקטי height < 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.
    }
}

בדיקת קטגוריות של גודל חלון

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

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

השלבים הבאים

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

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