מפתחים נתקלים לעיתים קרובות בקשיים ייחודיים כשהם יוצרים אפליקציות למכשירים מתקפלים – במיוחד למכשירים כמו Samsung Trifold או Pixel Fold המקורי, שנפתח בפורמט לרוחב (rotation_0 = landscape). דוגמאות לשגיאות של מפתחים:
- הנחות שגויות לגבי כיוון המכשיר
- תרחישי שימוש שלא נלקחו בחשבון
- החישוב מחדש או השמירה במטמון של ערכים נכשלים אחרי שינויים בהגדרות
דוגמאות לבעיות ספציפיות שקשורות למכשיר:
- חוסר התאמה בכיוון הטבעי של המכשיר בין המסכים החיצוני והפנימי (הנחות שמבוססות על rotation_0 = portrait), שגורם לכך שאפליקציות לא פועלות כשפותחים וסוגרים את המכשיר
- טיפול בשינוי הגדרות של צפיפות שגויות וצפיפות מסך שונה
- בעיות בתצוגה המקדימה של המצלמה שנגרמות בגלל התלות של חיישן המצלמה בכיוון הטבעי
כדי לספק חוויית משתמש באיכות גבוהה במכשירים מתקפלים, חשוב להתמקד בתחומים הקריטיים הבאים:
- קביעת הכיוון של האפליקציה על סמך שטח המסך בפועל שהאפליקציה תופסת, ולא על סמך הכיוון הפיזי של המכשיר
- עדכון התצוגות המקדימות של המצלמה כדי לנהל את כיוון המכשיר ויחסי הגובה-רוחב בצורה נכונה, למנוע תצוגות מקדימות לא ישרות ולמנוע תמונות מתוחות או חתוכות
- שמירה על רציפות השימוש באפליקציה בזמן קיפול או פתיחה של המכשיר, באמצעות שמירת המצב עם
ViewModelאו גישות דומות, או באמצעות טיפול ידני בשינויים בצפיפות המסך ובשינויים בכיוון, כדי למנוע הפעלה מחדש של האפליקציה או אובדן של המצב - באפליקציות שמשתמשות בחיישני תנועה, צריך להתאים את מערכת הקואורדינטות כך שתהיה בהתאמה לכיוון הנוכחי של המסך, ולהימנע מהנחות שמבוססות על rotation_0 = portrait, כדי להבטיח אינטראקציות מדויקות עם המשתמשים.
יצירת עיצוב דינמי
אם האפליקציה שלכם כבר מותאמת ועומדת בדרישות של רמת האופטימיזציה (רמה 2) שמפורטות בהנחיות לאיכות אפליקציות מותאמות, האפליקציה אמורה לפעול בצורה טובה במכשירים מתקפלים. אחרת, לפני שבודקים שוב את הפרטים הספציפיים של מכשירים מתקפלים עם שלושה חלקים ומכשירים מתקפלים במצב אופקי, כדאי לעיין במושגי הבסיס הבאים בנושא פיתוח אדפטיבי ל-Android.
פריסות מותאמות
ממשק המשתמש צריך להתמודד לא רק עם גדלים שונים של מסכים, אלא גם עם שינויים בזמן אמת ביחס הגובה-רוחב, כמו פתיחה של המכשיר וכניסה למצב של ריבוי חלונות או ממשק מחשב. במאמר מידע על פריסות דינמיות מוסבר איך:
- עיצוב והטמעה של פריסות מותאמות
- התאמת הניווט הראשי באפליקציה בהתאם לגודל החלון
- שימוש במחלקות של גודל החלון כדי להתאים את ממשק המשתמש של האפליקציה
- פשוט יותר להטמיע פריסות קנוניות, כמו רשימה ופירוט, באמצעות Jetpack APIs
סיווג לפי גודל חלון
במכשירים מתקפלים, כולל מכשירים מתקפלים לרוחב ומכשירים מתקפלים לשלושה חלקים, אפשר לעבור באופן מיידי בין גדלים שונים של חלונות: קומפקטי, בינוני ומוגדל. הבנה של המחלקות האלה והטמעה שלהן מבטיחות שהאפליקציה תציג את רכיבי הניווט הנכונים ואת צפיפות התוכן הנכונה בהתאם למצב הנוכחי של המכשיר.
בדוגמה הבאה נעשה שימוש בספריית Material 3 Adaptive כדי לקבוע כמה מקום פנוי יש לאפליקציה. לשם כך, קודם מפעילים את הפונקציה currentWindowAdaptiveInfo(), ואז משתמשים בפריסות המתאימות לשלוש קטגוריות הגודל של החלון:
val adaptiveInfo = currentWindowAdaptiveInfo()
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 ומעלה. האפליקציות ממלאות את חלון התצוגה, בלי קשר ליחס הגובה-רוחב או להעדפת הכיוון של המשתמש, ולא נעשה יותר שימוש במצב התאימות letterboxing.
שיקולים מיוחדים
למכשירים מתקפלים עם שלושה חלקים ולמכשירים מתקפלים לרוחב יש התנהגויות ייחודיות של החומרה שדורשות טיפול ספציפי, במיוחד בכל מה שקשור לחיישנים, לתצוגה מקדימה של המצלמה ולרציפות ההגדרה (שמירה על המצב כשמקפלים, פותחים או משנים את הגודל).
תצוגה מקדימה של המצלמה
בעיה נפוצה במכשירים מתקפלים במצב אופקי או בחישובים של יחסי גובה-רוחב (בתרחישים כמו ריבוי חלונות, ממשק מחשב או מסכים מחוברים), היא שהתצוגה המקדימה של המצלמה מופיעה מתוחה, הצידה, חתוכה או מסובבת.
הנחות לא תואמות
הבעיה הזו מתרחשת לעיתים קרובות במסכים גדולים ובמכשירים מתקפלים, כי אפליקציות יכולות להניח שיש קשר קבוע בין תכונות של המצלמה – כמו יחס רוחב-גובה וכיוון החיישן – לבין תכונות של המכשיר – כמו כיוון המכשיר והכיוון הטבעי.
סוגי מכשירים חדשים מאתגרים את ההנחה הזו. במכשיר מתקפל, גודל התצוגה ויחס הגובה-רוחב יכולים להשתנות בלי שהמכשיר יסתובב. לדוגמה, פתיחת מכשיר משנה את יחס הגובה-רוחב, אבל אם המשתמש לא מסובב את המכשיר, הסיבוב שלו נשאר זהה. אם אפליקציה מניחה שיש קשר בין יחס הגובה-רוחב לבין סיבוב המכשיר, יכול להיות שהיא תסובב או תשנה את גודל התצוגה המקדימה של המצלמה בצורה שגויה. אותו הדבר יכול לקרות אם אפליקציה מניחה שהכיוון של חיישן המצלמה תואם לכיוון של מכשיר במצב פורטרט, מה שלא תמיד נכון לגבי מכשירים מתקפלים במצב לרוחב.
פתרון 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 של Jetpack WindowManager כמו FoldingFeature.
מידע נוסף על מצבים, סטטוסים ותמיכה בתצוגה מקדימה של המצלמה במכשירים מתקפלים זמין במדריכי המפתחים הבאים:
מכשירים מתקפלים מציעים חוויית צפייה ייחודית. מצב תצוגה אחורית ומצב מסך מפוצל מאפשרים לכם ליצור תכונות תצוגה מיוחדות למכשירים מתקפלים, כמו תצוגה מקדימה של סלפי במצלמה האחורית ותצוגות שונות בו-זמנית במסכים הפנימיים והחיצוניים. מידע נוסף זמין במאמרים הבאים:
נעילת הכיוון לכיוון החיישן הטבעי
בתרחישי שימוש ספציפיים מאוד – בפרט, באפליקציות שצריכות להשתלט על המסך כולו בלי קשר למצב הקיפול של המכשיר – הדגל nosensor מאפשר לכם לנעול את האפליקציה לכיוון הטבעי של המכשיר. לדוגמה, ב-Pixel Fold, האוריינטציה הטבעית של המכשיר כשהוא מקופל היא לאורך, ואילו האוריינטציה הטבעית כשהוא פתוח היא לרוחב. הוספת הדגל nosensor מאלצת את האפליקציה להיות נעולה במצב אנכי כשהיא פועלת במסך החיצוני, ונעולה במצב אופקי כשהיא פועלת במסך הפנימי.
<activity
android:name=".MainActivity"
android:screenOrientation="nosensor">
מיפוי מחדש של חיישני XR במשחקים
במשחקים ובאפליקציות XR, נתוני חיישנים גולמיים (כמו גירוסקופ או מד תאוצה) מסופקים במערכת קואורדינטות קבועה במכשיר. אם המשתמש מסובב את המכשיר כדי לשחק במשחק במצב לרוחב, צירי החיישן לא מסתובבים עם המסך, ולכן אמצעי הבקרה של המשחק לא פועלים בצורה נכונה.
כדי לפתור את הבעיה, בודקים את הערך הנוכחי של Display.getRotation() וממפים מחדש את הצירים בהתאם:
- סיבוב 0: x=x, y=y
- Rotation 90: x=-y, y=x
- סיבוב ב-180 מעלות: x=-x, y=-y
- סיבוב של 270 מעלות: x=y, y=-x
כדי להשתמש בווקטורי סיבוב (במצפן או באפליקציות XR), צריך להשתמש ב-SensorManager.remapCoordinateSystem() כדי למפות את הכיוון של עדשת המצלמה או את החלק העליון של המסך לצירים החדשים על סמך הסיבוב הנוכחי.
תאימות האפליקציה
האפליקציות צריכות לעמוד בהנחיות האיכות כדי להבטיח תאימות בכל גורמי הצורה ובמסכים מחוברים. אם אפליקציה לא עומדת בהנחיות, יצרני המכשירים יכולים להטמיע אמצעים לשיפור התאימות, אבל יכול להיות שהדבר יפגע בחוויית המשתמש.
כדי לקבל מידע נוסף, כדאי לעיין ברשימה המקיפה של פתרונות עקיפים לבעיות תאימות שמופיעה בפלטפורמה, במיוחד אלה שקשורים לתצוגה מקדימה של המצלמה, שינויים בערכי ברירת מחדל ושינויים ב-API של Android 16 שיכולים לשנות את התנהגות האפליקציה.
מידע נוסף על פיתוח אפליקציות מותאמות זמין בהנחיות בנושא איכות אפליקציות מותאמות.