תמיכה במכשירים מתקפלים עם שלושה מסכים ובמכשירים מתקפלים לרוחב

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

מפתחים נתקלים לעיתים קרובות בקשיים ייחודיים כשהם יוצרים אפליקציות למכשירים מתקפלים – במיוחד למכשירים כמו Samsung Trifold או Pixel Fold המקורי, שנפתח בפורמט לרוחב (rotation_0 = landscape). דוגמאות לשגיאות של מפתחים:

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

דוגמאות לבעיות ספציפיות שקשורות למכשיר:

  • חוסר התאמה בכיוון הטבעי של המכשיר בין המסך החיצוני למסך הפנימי (הנחות על סמך rotation_0 = portrait), שגורם לכך שאפליקציות נכשלות בתהליכי קיפול ופתיחה
  • טיפול בשינוי הגדרות של צפיפות שגויות וערכי צפיפות מסך שונים
  • בעיות בתצוגה המקדימה של המצלמה שנגרמות בגלל התלות של חיישן המצלמה בכיוון הטבעי

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

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

יצירת עיצוב דינמי

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

פריסות מותאמות

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

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

סיווג לפי גודל החלון

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

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

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

val adaptiveInfo = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)
val windowSizeClass = adaptiveInfo.windowSizeClass

when {
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> // Large
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> // Medium
  else -> // Compact
}

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

איכות האפליקציה במסך גדול

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

‫Android מגרסה 16 ואילך

באפליקציות שמיועדות ל-Android 16 (רמת API‏ 36) ומעלה, המערכת מתעלמת מהגבלות על כיוון, שינוי גודל ויחס גובה-רוחב במסכים עם רוחב מינימלי של ‎600dp ומעלה. האפליקציות ממלאות את כל חלון התצוגה, ללא קשר ליחס הגובה-רוחב או להעדפת הכיוון של המשתמש, ומצב התאימות של הוספת פסים שחורים לא נמצא יותר בשימוש.

שיקולים מיוחדים

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

תצוגה מקדימה של המצלמה

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

הנחות שונות

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

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

פתרון 1: Jetpack CameraX (המומלץ)

הפתרון הפשוט והיציב ביותר הוא שימוש בספריית CameraX של Jetpack. רכיב ממשק המשתמש PreviewView נועד לטפל בכל המורכבויות של התצוגה המקדימה באופן אוטומטי:

  • PreviewView מתבצעת התאמה נכונה לכיוון החיישן, לסיבוב המכשיר ולשינוי הגודל.
  • הוא שומר על יחס הגובה-רוחב של התמונה מהמצלמה, בדרך כלל על ידי מרכוז וחיתוך (FILL_CENTER).
  • אפשר להגדיר את סוג הסקאלה ל-FIT_CENTER כדי להוסיף מסגרת שחורה לתצוגה המקדימה אם צריך.

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

פתרון 2: CameraViewfinder

אם אתם משתמשים בבסיס קוד קיים של Camera2, ספריית CameraViewfinder (תואמת לדורות קודמים עד לרמת API 21) היא פתרון מודרני נוסף. הוא מפשט את הצגת פיד המצלמה באמצעות TextureView או SurfaceView ומחיל את כל השינויים הנדרשים (יחס גובה-רוחב, קנה מידה וסיבוב) בשבילכם.

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

פתרון 3: הטמעה ידנית של Camera2

אם אי אפשר להשתמש ב-CameraX או ב-CameraViewfinder, צריך לחשב באופן ידני את כיוון התמונה ויחס הגובה-רוחב שלה, ולוודא שהחישובים מתעדכנים בכל שינוי בהגדרות:

  • מקבלים את כיוון חיישן המצלמה (לדוגמה, 0, 90, 180, 270 מעלות) מ- CameraCharacteristics.
  • קבלת זווית הסיבוב הנוכחית של המסך במכשיר (לדוגמה, 0, 90, 180, 270 מעלות).
  • משתמשים בשני הערכים האלה כדי לקבוע את השינויים הנדרשים עבור SurfaceView או TextureView.
  • כדי למנוע עיוות, חשוב לוודא שיחס הגובה-רוחב של הפלט Surface זהה ליחס הגובה-רוחב של התצוגה המקדימה של המצלמה.
  • יכול להיות שאפליקציית המצלמה פועלת בחלק מהמסך, במצב ריבוי חלונות או במצב ממשק מחשב, או במסך מחובר. לכן, אסור להשתמש בגודל המסך כדי לקבוע את המימדים של העינית במצלמה, אלא צריך להשתמש במדדי החלון.

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

פתרון 4: ביצוע פעולות בסיסיות במצלמה באמצעות Intent

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

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

הגדרה והמשכיות

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

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

שינויי התצורה שמופעלים לעיתים קרובות כוללים screenSize,‏ smallestScreenSize,‏ screenLayout,‏ orientation,‏ density,‏ fontScale,‏ touchscreen ו-keyboard.

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

שינויים בהגדרות הצפיפות

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

הגדרה של AndroidManifest.xml

  • density: הצהרה שהאפליקציה תטפל בשינוי צפיפות המסך
  • שינויים אחרים בהגדרות: מומלץ גם להצהיר על שינויים אחרים בהגדרות שמתרחשים לעיתים קרובות, למשל screenSize, orientation, keyboardHidden, fontScale וכו'.

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

הטמעה של onConfigurationChanged()‎

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

  • מוודאים שערך ה-DPI השתנה ל-newConfig.densityDpi
  • איפוס תצוגות מותאמות אישית, רכיבי drawable מותאמים אישית וכו' לצפיפות החדשה

פריטי משאבים לעיבוד

  • משאב תמונה: החלפת מפות סיביות ומשאבים מסוג drawable במשאבים ספציפיים לצפיפות, או התאמת קנה המידה ישירות
  • יחידת פריסה (המרת dp ל-px): חישוב מחדש של גודל התצוגה, השוליים והריווח הפנימי
  • גודל הגופן והטקסט: החלה מחדש של גודל הטקסט ביחידת sp
  • שרטוט View/Canvas בהתאמה אישית: עדכון הערכים מבוססי הפיקסלים שמשמשים לשרטוט Canvas

קביעת הכיוון של האפליקציה

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

פתרון 1: שימוש ב-Configuration.orientation

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

פתרון 2: שימוש ב-WindowMetrics#getBounds()

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

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

תנוחות ומצבי תצוגה

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

כדי להבדיל בין האפליקציה שלכם במכשירים מתקפלים שתומכים ב-HALF_OPENED, אתם יכולים להשתמש בממשקי API של Jetpack WindowManager, כמו FoldingFeature.

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

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

נעילת הכיוון לכיוון החיישן הטבעי

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

<activity
  android:name=".MainActivity"
  android:screenOrientation="nosensor">

מיפוי מחדש של משחקים וחיישני XR

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

כדי לפתור את הבעיה, בודקים את הערך הנוכחי של Display.getRotation()‎ וממפים מחדש את הצירים בהתאם:

  • סיבוב 0: x=x, y=y
  • סיבוב ב-90 מעלות: x=-y, y=x
  • סיבוב של 180 מעלות: x=-x, y=-y
  • סיבוב של 270 מעלות: x=y, y=-x

כדי להשתמש בווקטורי סיבוב (במצפן או באפליקציות XR), צריך להשתמש ב-SensorManager.remapCoordinateSystem() כדי למפות את כיוון עדשת המצלמה או את החלק העליון של המסך לצירים החדשים על סמך הסיבוב הנוכחי.

תאימות האפליקציה

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

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

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