עיצוב רספונסיבי/מותאם אישית עם תצוגות

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

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

עיצוב רספונסיבי

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

ConstraintLayout

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

הדרך הקלה ביותר ליצור פריסה באמצעות ConstraintLayout היא להשתמש בכלי Layout Editor ב-Android Studio. בעזרת הכלי Layout Editor, אתם יכולים לגרור תצוגות חדשות לפריסה, להחיל אילוצים ביחס לתצוגות אב ותצוגות מקבילות ולהגדיר מאפיינים של תצוגות – והכול בלי לערוך קובצי XML באופן ידני.

איור 3. ‫Layout Editor ב-Android Studio עם ConstraintLayout.

מידע נוסף זמין במאמר יצירת ממשק משתמש רספונסיבי באמצעות ConstraintLayout.

רוחב וגובה דינמיים

כדי שהפריסה תהיה רספונסיבית לגדלים שונים של מסכים, צריך להשתמש ב-wrap_content, ב-match_parent או ב-0dp (match constraint) לרוחב ולגובה של רכיבי התצוגה במקום בערכים שמוגדרים בהארדקוד:

  • wrap_content: הגודל של התצוגה מוגדר כך שיתאים לתוכן שמוצג בה.
  • match_parent: התצוגה מתרחבת ככל האפשר בתוך תצוגת ההורה.
  • 0dp (match constraint): בפורמט ConstraintLayout, כמו match_parent. התצוגה תופסת את כל המקום שזמין במסגרת המגבלות של התצוגה.

לדוגמה:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/lorem_ipsum" />

תמונה 4 מראה איך הרוחב והגובה של TextView משתנים כשהרוחב של התצוגה משתנה בהתאם לכיוון המכשיר.

איור 4. TextView רספונסיבי.

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

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

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

עיצוב אדפטיבי

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

‫SlidingPaneLayout לממשקי משתמש עם רשימה ופירוט

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

SlidingPaneLayout מנהל את הלוגיקה לקביעה איזו מבין שתי חוויות המשתמש מתאימה לגודל החלון הנוכחי:

<?xml version="1.0" encoding="utf-8"?>
<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/item_navigation" />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>

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

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

משאבי פריסה חלופיים

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

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

אתם יכולים ליצור פריסות מותאמות למסך על ידי יצירת ספריות res/layout/ נוספות בקוד המקור של האפליקציה. יוצרים ספרייה לכל הגדרת תצורה של מסך שנדרש עבורה פריסה שונה. לאחר מכן מוסיפים מחרוזת להגדרת תצורה של מסך לשם התיקייה layout (לדוגמה, layout-w600dp למסכים עם רוחב זמין של 600dp).

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

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

המסנן של הרוחב הקטן ביותר

המסווג smallest width (הרוחב המינימלי) של גודל המסך מאפשר לספק פריסות חלופיות למסכים עם רוחב מינימלי שנמדד בפיקסלים שלא תלויים בדחיסות (dp).

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

לדוגמה, אפשר ליצור פריסה בשם main_activity שמיועדת לטלפונים ולטאבלטים, על ידי יצירת גרסאות שונות של הקובץ בספריות שונות:

res/layout/main_activity.xml           # For phones (smaller than 600dp smallest width)
res/layout-sw600dp/main_activity.xml   # For 7" tablets (600dp wide or wider)

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

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

  • ‫320dp: מסך קטן של טלפון (240x320 ldpi,‏ 320x480 mdpi,‏ 480x800 hdpi וכו')
  • ‫480dp: מסך גדול של טלפון, בערך 5" (480x800 mdpi)
  • ‫600dp: טאבלט בגודל 7 אינץ' (600x1024 mdpi)
  • ‫720dp: טאבלט בגודל 10 אינץ' (720x1280 mdpi,‏ 800x1280 mdpi וכו')

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

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

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

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

מסנן רוחב זמין

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

res/layout/main_activity.xml         # For phones (smaller than 600dp available width)
res/layout-w600dp/main_activity.xml  # For 7" tablets or any screen with 600dp available width
                                     # (possibly landscape phones)

אם הגובה הזמין הוא בעיה באפליקציה שלכם, אתם יכולים להשתמש במגביל available height. לדוגמה, layout-h600dp למסכים עם גובה של לפחות 600dp.

מחרוזות להגדרת כיוון

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

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

res/layout/main_activity.xml                # For phones
res/layout-land/main_activity.xml           # For phones in landscape
res/layout-sw600dp/main_activity.xml        # For 7" tablets
res/layout-sw600dp-land/main_activity.xml   # For 7" tablets in landscape

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

גדלים של חלונות

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

כדי להחיל פריסות דינמיות באופן אוטומטי:

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

מידע נוסף זמין במאמר בנושא סיווגים של גודל חלון.

רכיבי ממשק משתמש מודולריים באמצעות פרגמנטים

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

לדוגמה, אפשר להטמיע את התבנית list-detail (ראו SlidingPaneLayout למעלה) באמצעות fragment אחד שמכיל את הרשימה ו-fragment אחר שמכיל את פרטי הפריט ברשימה. במסכים גדולים, יכול להיות שהקטעים יוצגו זה לצד זה. במסכים קטנים, הם יוצגו בנפרד וימלאו את המסך.

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

הטמעה של פעילות

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

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

אתם קובעים איך הפעילויות באפליקציה יוצגו על ידי יצירת קובץ תצורת XML. המערכת משתמשת בקובץ הזה כדי לקבוע את אופן ההצגה המתאים על סמך גודל התצוגה. אפשר גם לבצע קריאות ל-Jetpack WindowManager API.

הטמעת פעילויות תומכת בשינויים בכיוון המכשיר ובמכשירים מתקפלים. הפעילויות נערמות או מוסרות מהערמה כשהמכשיר מסתובב או מתקפל ונפרש.

מידע נוסף זמין במאמר בנושא הטמעת פעילות.

גדלים של מסכים ויחסי גובה-רוחב

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

‫Android מגרסה 10 (רמת API‏ 29) ואילך תומך במגוון רחב של יחסי גובה-רוחב. גורמי הצורה של מכשירים מתקפלים יכולים להיות מגוונים, ממסכים צרים וארוכים כמו 21:9 כשהמכשיר מקופל, ועד ליחס גובה-רוחב ריבועי של 1:1 כשהמכשיר פתוח.

כדי להבטיח תאימות לכמה שיותר מכשירים, מומלץ לבדוק את האפליקציות שלכם בכמה שיותר מיחסי הגובה-רוחב הבאים של המסך:

איור 7. יחסי גובה-רוחב שונים של המסך.

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

אם אתם מעדיפים לבצע בדיקה במכשיר אמיתי אבל אין לכם מכשיר כזה, אתם יכולים להשתמש ב-Firebase Test Lab כדי לגשת למכשירים במרכז נתונים של Google.

מקורות מידע נוספים