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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

val adaptiveInfo = currentWindowAdaptiveInfoV2()
val windowSizeClass = adaptiveInfo.windowSizeClass

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

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

איכות אדפטיבית של אפליקציות

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

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

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

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

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

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

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

הנחות שונות

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

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

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

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

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

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

פתרון 2: CameraViewfinder

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

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

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

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

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

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

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

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

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

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

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

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

כדי לבדל את האפליקציה במכשירים מתקפלים שתומכים ב-HALF_OPENED, משתמשים בממשקי API מותאמים של Compose Material 3, כמו collectFoldingFeaturesAsState() ו-currentWindowAdaptiveInfoV2().

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

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

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

במקרים ספציפיים מאוד – במיוחד באפליקציות שצריכות להשתלט על המסך כולו בלי קשר למצב הקיפול של המכשיר – אפשר להשתמש בדגל 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 שיכולים לשנות את התנהגות האפליקציה.

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