תמיכה בדחיסות פיקסלים שונה

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

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

לקבלת סקירה כללית של הטכניקות האלה, צפו בסרטון הבא.

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

שימוש בפיקסלים שלא תלויים בדחיסות

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

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

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

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

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

לדוגמה, כדי לציין ריווח בין שתי תצוגות, משתמשים ב-dp:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

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

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

המרת יחידות dp ליחידות פיקסלים

במקרים מסוימים, צריך לציין את המאפיינים ב-dp ואז להמיר אותם לפיקסלים. המרה של יחידות dp לפיקסלים במסך הוא:

px = dp * (dpi / 160)

הערה: אף פעם אל תכתבו את המשוואה הזו בתוך הקוד כדי לחשב פיקסלים. במקום זאת, השתמשו TypedValue.applyDimension(), שממירה סוגים רבים של מאפיינים (dp, sp וכו') לפיקסלים.

דמיינו אפליקציה שבה המערכת מזהה תנועה של גלילה או החלקה אחרי שהאצבע של המשתמש זזה לפחות ב-16 פיקסלים. לפי ערך הבסיס האצבע של המשתמש חייבת להזיז את 16 pixels / 160 dpi, ששווה ל-1/10 מ"מ, לפני התנועה מזוהה.

במכשיר בתצוגה בצפיפות גבוהה (240dpi), האצבע של המשתמש חייבת לזוז 16 pixels / 240 dpi, שווה ל-1/15 אינץ' (או 1.7 מ"מ). המרחק קצר בהרבה, לכן האפליקציה נראית רגישה יותר למשתמש.

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

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

השדה DisplayMetrics.density מציין את גורם קנה המידה שמשמש להמרה של יחידות dp פיקסלים בהתאם לדחיסות הפיקסלים הנוכחית. במסך עם צפיפות בינונית, DisplayMetrics.density שווה 1.0, ובמסך עם צפיפות גבוהה היא שווה 1.5. במסך עם צפיפות גבוהה במיוחד, הוא שווה ל-2.0, ובמסך עם צפיפות נמוכה הוא שווה 0.75. דמות זו בשימוש על ידי TypedValue.applyDimension() כדי לקבל את מספר הפיקסלים בפועל של המסך הנוכחי.

שימוש בערכי הגדרות מותאמים מראש

אפשר להשתמש במחלקה ViewConfiguration כדי לגשת המרחקים, המהירויות והזמנים שבהם מערכת Android משתמשת. לדוגמה, המרחק בפיקסלים ששימש את המסגרת בתור ערך סף הגלילה עם getScaledTouchSlop():

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

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

העדפה לגרפיקה וקטורית

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

גרפיקה וקטורית מסופקת בדרך כלל כקובצי SVG (Scalable Vector Graphics), אבל Android לא תומך בפורמט הזה ולכן צריך להמיר קובצי SVG הווקטור של Android בפורמט ניתן להזזה.

אפשר להמיר קובץ SVG לפורמט וקטורי שניתן לשרטוט באמצעות Vector Asset Studio ככה:

  1. בחלון Project, לוחצים לחיצה ימנית על הספרייה res ובוחרים חדש > נכס וקטורי.
  2. בוחרים באפשרות קובץ מקומי (SVG, PSD).
  3. מאתרים את הקובץ שרוצים לייבא ומבצעים את השינויים הרצויים.

    תמונה שמראה איך לייבא קובצי SVG ב-Android Studio
    איור 2: ייבוא קובץ SVG עם ב-Android Studio.

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

  4. לוחצים על הבא.

  5. במסך הבא, מאשרים את קבוצת המקור שאליה רוצים לצרף את הקובץ בפרויקט. ולוחצים על Finish (סיום).

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

    res/
      drawable/
        ic_android_launcher.xml
    

מידע נוסף על יצירת גרפיקה וקטורית זמין במאמר קובץ גרפיקה וקטורי התיעוד.

שימוש במפות סיביות חלופיות

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

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

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

טבלה 1. מגדירי הגדרות אישיות צפיפות פיקסלים.

תוחם הצפיפות תיאור
ldpi משאבים למסכים עם צפיפות נמוכה (ldpi) (כ-120dpi).
mdpi משאבים למסכים עם צפיפות בינונית (mdpi) ( ~160 dpi). זהו ערך הבסיס צפיפות.
hdpi משאבים למסכים בצפיפות גבוהה (hdpi) (כ-240dpi).
xhdpi משאבים למסכים בצפיפות גבוהה במיוחד (xhdpi) (כ-320dpi).
xxhdpi משאבים למסכים בצפיפות גבוהה במיוחד (xxhdpi) (כ-480dpi).
xxxhdpi משאבים לשימוש בצפיפות גבוהה במיוחד (xxxhdpi) (כ-640dpi).
nodpi משאבים לכל ערכי צפיפות. אלו משאבים שלא תלויים בדחיסות. המערכת לא משאבים שתויגו באמצעות המאפיין הזה, ללא קשר לצפיפות המסך הנוכחי.
tvdpi מקורות מידע למסכים במיקומים בין mdpi ל-hdpi. בערך כ-213dpi זה לא נחשב לדף 'ראשי' קבוצת צפיפות. המטרה היא בעיקר בטלוויזיות וברוב האפליקציות לא צריך אותו – כדי לספק mdpi ו-hdpi מספיקים עבור רוב האפליקציות, והמערכת מתאימה אותם המתאים. אם יהיה צורך לספק מקורות מידע של tvdpi, את הגודל שלהם לפי גורם של 1.33 * mdpi. לדוגמה, תמונה בגודל 100x100 פיקסלים, מסכי mdpi הם 133x133 פיקסלים ל-tvdpi.

כדי ליצור פריטים חלופיים למפת סיביות (bitmap) לצפיפות שונה, צריך לפעול לפי יחס גובה-רוחב של 3:4:6:8:12:16 בין שש רמות הדחיסות העיקריות. לדוגמה, אם יש לך ניתן לצייר מפת סיביות (bitmap) בגודל 48x48 פיקסלים למסכים עם צפיפות בינונית. הגדלים הם:

  • 36x36 (0.75x) לצפיפות נמוכה (ldpi)
  • 48x48 (ערך בסיס של 1.0x) לצפיפות בינונית (mdpi)
  • 72x72 (1.5x) לצפיפות גבוהה (hdpi)
  • 96x96 (2.0x) לצפיפות גבוהה במיוחד (xhdpi)
  • 144x144 (3.0x) לצפיפות גבוהה במיוחד (xxhdpi)
  • 192x192 (4.0x) לצפיפות גבוהה במיוחד (xxxhdpi)

שומרים את קובצי התמונות שנוצרו בספריית המשנה המתאימה. מתחת ל-res/:

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

לאחר מכן, בכל פעם שמתייחסים אל @drawable/awesomeimage, המערכת בוחרת את מפת הסיביות המתאימה בהתבסס על ה-DPI של המסך. אם לא מספקים משאב ספציפי לצפיפות עבור צפיפות זו, המערכת מאתרת את ההתאמה הטובה ביותר הבאה ומשנה אותה כך שתתאים למסך.

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

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

מיקום סמלי של אפליקציות בספריות של מיפוי זמני (mipmap)

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

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

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

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

בדוגמה הקודמת של מכשיר xxhdpi, אפשר לספק סמל מרכז האפליקציות בדחיסות גבוהה יותר בספרייה mipmap-xxxhdpi.

להנחיות לעיצוב סמלים, ראו סמלי מערכת.

לקבלת עזרה ביצירת סמלי אפליקציות, תוכלו לקרוא את המאמר יצירת סמלי אפליקציות באמצעות Image Asset Studio.

טיפים לבעיות לא נפוצות של צפיפות

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

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

  1. התאמה לעומס (pre-scaling) של משאבים, כמו פריטים ניתנים להזזה במפת סיביות

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

    אם מבקשים את המידות של משאב מותאם מראש, המערכת מחזירה ערכים שמייצגים את המאפיינים לאחר ההתאמה. לדוגמה, מפת סיביות שתוכננה בגודל 50x50 פיקסלים למסך mdpi מותאם ל-75x75 פיקסלים במסך hdpi (אם אין משאב חלופי) ל-hdpi), והמערכת מדווחת על הגודל בהתאם.

    יש מצבים מסוימים שבהם לא כדאי ל-Android לבצע התאמה מראש משאב. הדרך הקלה ביותר למנוע התאמה לעומס (pre-scaling) היא לשמור את המשאב בספריית משאבים עם תוחם ההגדרה nodpi. לדוגמה:

    res/drawable-nodpi/icon.png

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

  2. התאמה אוטומטית של מידות וקואורדינטות של פיקסלים

    אפשר להשבית את ההתאמה מראש של מידות ותמונות באמצעות ההגדרה android:anyDensity ל-"false" במניפסט או באופן פרוגרמטי עבור Bitmap, על ידי הגדרה של inScaled ל-"false". לחשבון במקרה כזה, המערכת מבצעת התאמה אוטומטית של גודל ופיקסלים מוחלטים של פיקסלים בזמן המשיכה. הוא עושה זאת כדי להבטיח רכיבי מסך עדיין יוצגו באותו גודל פיזי בערך שיהיה אפשר להציג אותם בצפיפות הפיקסלים הבסיסית (mdpi). המערכת מטפלת את ההתאמה הזו לעומס (scaling) באופן שקוף, ומדווחת על הפיקסל המותאם מאפיינים חדשים לאפליקציה, במקום מימדי פיקסלים פיזיים.

    לדוגמה, נניח שמכשיר כולל מסך בצפיפות גבוהה של WVGA, בגודל 480x800 זהה לגודל של מסך HVGA מסורתי – אבל הוא פועל על אפליקציה שהושבתה להתאמה לעומס (scaling). במקרה הזה, המערכת "שוכבת" לאפליקציה כשמתבצעת שאילתה על המסך ומדווח 320x533, תרגום ה-mdpi המשוער לדחיסות הפיקסלים.

    לאחר מכן, האפליקציה מבצעת פעולות שרטוט, כמו ביטול התוקף של מלבן מ-(10,10) ל-(100, 100), המערכת משנה את הקואורדינטות על ידי התאמת גודלן, ולמעשה מבטל את התוקף של האזור (15,15) ל-(150, 150). חוסר התאמה זה עלול לגרום להתנהגות לא צפויה אם האפליקציה מבצעת מניפולציה ישירה במפת סיביות מותאמת, אבל הפעולה הזו נחשבת איזון כדי להבטיח את ביצועי האפליקציה הטובים ביותר. אם נתקלתם בבעיה הזו זהו המצב, אפשר לקרוא את המאמר המרת יחידות dp לפיקסלים יחידות מידה.

    בדרך כלל לא משביתים את ההתאמה מראש לעומס (pre-scaling). הדרך הטובה ביותר לתמוך בריבוי במסכים פועלים לפי השיטות הבסיסיות שמתוארות בדף הזה.

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

בדיקה לגבי כל דחיסות הפיקסלים

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

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