1. מבוא
הסביבה העסקית של מכשירי Android מתפתחת כל הזמן. מהימים המוקדמים של מקלדות חומרה מובנות ועד לנוף המודרני של מכשירים ניידים, מכשירים מתקפלים, טאבלטים וחלונות שניתן לשנות את הגודל שלהם בצורה חופשית – האפליקציות ל-Android מעולם לא היו מגוונות יותר מאשר היום.
אלה חדשות טובות למפתחים, אבל אופטימיזציות מסוימות של אפליקציות נדרשות כדי לעמוד בציפיות של נוחות השימוש וכדי ליצור חוויית משתמש מצוינת בגדלים שונים של מסכים. במקום לטרגט כל מכשיר חדש בנפרד, בעזרת ממשק משתמש רספונסיבי/מותאם אישית וארכיטקטורה עמידה אפשר לגרום לאפליקציה להיראות ולעבוד מעולה בכל מקום שבו נמצאים המשתמשים הנוכחיים והעתידיים שלכם – במכשירים בכל הגדלים והצורות!
ההשקה של סביבות Android שניתן לשנות את הגודל שלהן בפריסה גמישה היא דרך נהדרת לבדוק את ממשק המשתמש הרספונסיבי או המותאם אישית שלכם, כך שהוא יהיה מוכן לכל מכשיר. שיעור ה-Lab הזה ינחה אתכם בהבנת ההשלכות של שינוי הגודל, וביישום של כמה שיטות מומלצות שיעזרו לכם לשנות את גודל האפליקציה בצורה נוחה ופשוטה.
מה תפַתחו
תלמדו על ההשלכות של שינוי גודל חופשי וביצוע אופטימיזציה לאפליקציה ל-Android כדי להדגים שיטות מומלצות לשינוי גודל. האפליקציה שלך:
קובץ מניפסט תואם
- הסרת הגבלות שמונעות מאפליקציה לשנות את הגודל
שמירה על המצב לאחר שינוי גודל
- הוא שומר על המצב של ממשק המשתמש כשמשנים את הגודל באמצעות האפשרות rememberSaveable
- כדי לאתחל את ממשק המשתמש, מומלץ להימנע מכפילויות לא נחוצות של עבודה ברקע
למה תזדקק?
- ידע ביצירת אפליקציות בסיסיות ל-Android
- ידע ב-ViewModel ו-State ב-Compose
- מכשיר בדיקה שתומך בשינוי גודל של חלון בצורה חופשית, כמו אחת מהאפשרויות הבאות:
- Chromebook עם הגדרת ADB
- טאבלט שתומך במצב Samsung DeX או במצב פרודוקטיביות
- אמולטור מכשיר וירטואלי של Android למחשב ב-Android Studio
אם נתקלתם בבעיות (באגים בקוד, שגיאות דקדוק, ניסוח לא ברור וכו') במהלך העבודה ב-Codelab, אפשר לדווח על הבעיה דרך הקישור דיווח על טעות בפינה הימנית התחתונה של ה-Codelab.
2. תחילת העבודה
משכפלים את המאגר מ-GitHub.
git clone https://github.com/android/large-screen-codelabs/
...או מורידים קובץ ZIP של המאגר ומחלצים אותו
ייבוא פרויקט
- פתיחת Android Studio
- בוחרים באפשרות Import Project (ייבוא פרויקט) או באפשרות File->New->Import Project (קובץ > חדש)
- עוברים למקום שבו שכפול או חילצת את הפרויקט
- פותחים את התיקייה שינוי גודל.
- פותחים את הפרויקט בתיקייה start. הערך הזה מכיל את הקוד לתחילת הפעולה.
רוצה לנסות את האפליקציה
- יצירה והפעלה של האפליקציה
- כדאי לנסות לשנות את גודל האפליקציה
מה דעתך?
בהתאם לתמיכה בתאימות של מכשיר הבדיקה, סביר להניח ששמת לב שחוויית המשתמש לא אידיאלית. לא ניתן לשנות את גודל האפליקציה והיא נתקעת ביחס הגובה-רוחב הראשוני. מה קורה?
הגבלות במניפסט
אם תסתכלו בקובץ AndroidManifest.xml
של האפליקציה, תוכלו לראות שנוספו לה כמה הגבלות שמונעות מהאפליקציה שלנו לפעול כראוי בסביבה שניתנת לשינוי גודל של חלון.
AndroidManifest.xml
android:maxAspectRatio="1.4"
android:resizeableActivity="false"
android:screenOrientation="portrait">
כדאי לנסות להסיר את שלוש השורות הבעייתיות מהמניפסט, לבנות מחדש את האפליקציה ולנסות שוב במכשיר הבדיקה. תוכלו לראות שכבר לא חלה הגבלה על האפשרות לשנות את הגודל של האפליקציה במצב חופשי. הסרה של הגבלות כאלה מהמניפסט היא שלב חשוב באופטימיזציה של האפליקציה לשינוי הגודל של החלון באופן חופשי.
3. שינויים בהגדרות של שינוי הגודל
כשמשנים את גודל החלון של האפליקציה, ההגדרה של האפליקציה מתעדכנת. לעדכונים האלה יש השלכות על האפליקציה שלך. אם תבינו וציפייה, תוכלו לספק למשתמשים חוויה נהדרת. השינויים הבולטים ביותר הם הרוחב והגובה של חלון האפליקציה, אבל לשינויים האלה יש השלכות גם על יחס הגובה-רוחב והכיוון.
צפייה בשינויים בהגדרות האישיות
כדי לראות את השינויים האלה בעצמך באפליקציה שנוצרה באמצעות מערכת התצוגה של Android, אפשר לשנות את View.onConfigurationChanged
. ב-Jetpack פיתוח נייטיב, יש לנו גישה ל-LocalConfiguration.current
, ומתעדכן אוטומטית בכל פעם שמתקשרים ל-View.onConfigurationChanged
.
כדי לראות את שינויי ההגדרות האלה באפליקציה לדוגמה, צריך להוסיף לאפליקציה תוכן קומפוזבילי שמציג ערכים מ-LocalConfiguration.current
, או ליצור פרויקט חדש לדוגמה עם תוכן קומפוזבילי כזה. דוגמה לממשק משתמש להצגת הנכסים האלה:
val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
val screenLayoutSize =
when (configuration.screenLayout and
Configuration.SCREENLAYOUT_SIZE_MASK) {
SCREENLAYOUT_SIZE_SMALL -> "SCREENLAYOUT_SIZE_SMALL"
SCREENLAYOUT_SIZE_NORMAL -> "SCREENLAYOUT_SIZE_NORMAL"
SCREENLAYOUT_SIZE_LARGE -> "SCREENLAYOUT_SIZE_LARGE"
SCREENLAYOUT_SIZE_XLARGE -> "SCREENLAYOUT_SIZE_XLARGE"
else -> "undefined value"
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("screenWidthDp: ${configuration.screenWidthDp}")
Text("screenHeightDp: ${configuration.screenHeightDp}")
Text("smallestScreenWidthDp: ${configuration.smallestScreenWidthDp}")
Text("orientation: ${if (isPortrait) "portrait" else "landscape"}")
Text("screenLayout SIZE: $screenLayoutSize")
}
אתם יכולים לראות הטמעה לדוגמה בתיקיית הפרויקט observing-configuration-changes. כדאי לנסות להוסיף את המידע הזה לממשק המשתמש של האפליקציה, להריץ אותו במכשיר הבדיקה ולצפות בעדכון של ממשק המשתמש בזמן שינוי ההגדרות של האפליקציה.
השינויים האלה בתצורת האפליקציה מאפשרים לכם לדמות את המעבר במהירות מהקיצוניות המצופה שלנו, באמצעות מסך מפוצל במכשיר נייד קטן למסך מלא בטאבלט או במחשב. זו דרך טובה לבדוק את הפריסה של האפליקציה במסכים שונים, וגם לבדוק באיזו מידה האפליקציה יכולה להתמודד עם אירועי שינוי מהיר של הגדרות אישיות.
4. רישום אירועים במחזור החיים של הפעילות
עוד דוגמה לשינוי גודל החלון בפריסה גמישה באפליקציה היא השינויים השונים במחזור החיים של Activity
שיתרחשו באפליקציה. כדי לראות את השינויים האלה בזמן אמת, צריך להוסיף צופה במחזור החיים ל-method onCreate
ולרשום ביומן כל אירוע חדש במחזור החיים על ידי ביטול של onStateChanged
.
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d("resizing-codelab-lifecycle", "$event was called")
}
})
לאחר ההתחברות, מריצים שוב את האפליקציה במכשיר הבדיקה ומעיינים ב-logcat כשמנסים למזער את האפליקציה ולהציג אותה שוב בחזית.
שימו לב שהאפליקציה מושהית כשהיא ממוזערת, ואז היא ממשיכה לפעול שוב כשהיא תובא לחזית. יש לכך השלכות על האפליקציה שתחקור בקטע הבא של Codelab זה שמתמקד בהמשכיות.
עכשיו אפשר להיכנס ל-Logcat כדי לראות אילו קריאות חוזרות (callback) במחזור החיים של הפעילות כשמשנים את גודל האפליקציה מהגודל הקטן ביותר שלה לגודל הגדול ביותר האפשרי
בהתאם למכשיר הבדיקה, ייתכן שתבחינו בהתנהגויות שונות, אבל סביר להניח ששמתם לב שהפעילות שלכם נמחקת ונוצרת מחדש כשגודל החלון של האפליקציה משתנה באופן משמעותי, אבל לא כשיש שינוי קל. הסיבה לכך היא שב-API 24 ואילך, רק שינויים משמעותיים בגודל משפיעים על יצירה מחדש של Activity
.
ראיתם כמה משינויי ההגדרות הנפוצים שצפויים לקרות בסביבת חלונות בעיצוב חופשי, אבל יש שינויים אחרים שכדאי להיות מודעים להם. לדוגמה, אם חיברתם מסך חיצוני למכשיר הבדיקה, תוכלו לראות שה-Activity
מושמד ונוצר מחדש כדי להביא בחשבון שינויים בהגדרות כמו צפיפות התצוגה.
כדי להשמיט חלק מהמורכבות הכרוכות בשינויי ההגדרות, צריך להשתמש בממשקי API ברמה גבוהה יותר כמו WindowSizeClass כדי להטמיע את ממשק המשתמש המותאם. (ראו גם תמיכה בגודלי מסכים שונים).
5. המשכיות – תחזוקת תכנים קומפוזביליים מצב פנימי כשמשנים את הגודל
בקטע הקודם ראיתם חלק מהשינויים בהגדרות האפליקציה שצפויים לחולל שינוי גודל של חלון. בקטע הזה תראו שמצב ממשק המשתמש של האפליקציה יהיה רציף במהלך השינויים האלה.
בתור התחלה, צריך להרחיב את הפונקציה הקומפוזבילית NavigationDrawerHeader
(שנמצאת ב-ReplyHomeScreen.kt
) כדי להציג את כתובת האימייל כשלוחצים עליה.
@Composable
private fun NavigationDrawerHeader(
modifier: Modifier = Modifier
) {
var showDetails by remember { mutableStateOf(false) }
Column(
modifier = modifier.clickable {
showDetails = !showDetails
}
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ReplyLogo(
modifier = Modifier
.size(dimensionResource(R.dimen.reply_logo_size))
)
ReplyProfileImage(
drawableResource = LocalAccountsDataProvider
.userAccount.avatar,
description = stringResource(id = R.string.profile),
modifier = Modifier
.size(dimensionResource(R.dimen.profile_image_size))
)
}
AnimatedVisibility (showDetails) {
Text(
text = stringResource(id = LocalAccountsDataProvider
.userAccount.email),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.padding(
start = dimensionResource(
R.dimen.drawer_padding_header),
end = dimensionResource(
R.dimen.drawer_padding_header),
bottom = dimensionResource(
R.dimen.drawer_padding_header)
),
)
}
}
}
אחרי שמוסיפים לאפליקציה את הכותרת שניתנת להרחבה,
- להריץ את האפליקציה במכשיר הבדיקה
- צריך להקיש על הכותרת כדי להרחיב אותה
- לנסות לשנות את גודל החלון
אתם יכולים לראות שהכותרת מאבדת את המצב שלה כאשר משנים את הגודל שלה באופן משמעותי.
המצב של ממשק המשתמש אבד כי remember
עוזר לשמור את המצב בהרכבת מחדש, אבל לא במהלך יצירה מחדש של פעילות או עיבוד. מקובל להשתמש בהעלאת מצב של מדינות (state hoisting), והעברת המצב למצב קריאה של תוכן קומפוזבילי כדי להפוך תכנים קומפוזביליים ללא שמירת מצב, וכך אפשר למנוע לחלוטין את הבעיה הזו. עם זאת, אפשר להשתמש ב-remember
במקומות שבהם מצב הרכיב בממשק המשתמש נשאר פנימי לפונקציות קומפוזביליות.
כדי לפתור את הבעיות האלה, צריך להחליף את remember
בטקסט rememberSaveable
. הפעולה הזו פועלת כי rememberSaveable
שומר ומשחזר את הערך שנשמר ל-savedInstanceState
. צריך לשנות את ההגדרה remember
לערך rememberSaveable
, להריץ את האפליקציה במכשיר הבדיקה ולנסות שוב לשנות את גודל האפליקציה. תוכלו לראות שהמצב של הכותרת המתרחבת נשמר כפי שרציתם במהלך שינוי הגודל.
6. הימנעות מכפילויות מיותרות של עבודה ברקע
ראית איך אפשר להשתמש ב-rememberSaveable
כדי לשמר תכנים קומפוזביליים את המצב הפנימי של ממשק המשתמש באמצעות שינויי הגדרות שעשויים להתרחש לעיתים קרובות כתוצאה משינוי גודל של חלון. עם זאת, לעיתים קרובות האפליקציה צריכה להניח את המצב והלוגיקה של ממשק המשתמש כך שלא יהיו זמינים בתוכן קומפוזבילי. העברת בעלות על מצב ל-ViewModel היא אחת הדרכים הטובות ביותר לשמר את המצב במהלך שינוי הגודל. כשמעלים את המצב למצב ViewModel
, עלולות להתרחש בעיות כשמנסים לבצע פעולות ממושכות ברקע, כמו גישה נרחבת למערכת הקבצים או שיחות רשת שנחוצות לאתחול המסך.
כדי לראות דוגמה לסוגי הבעיות שאתם עשויים להיתקל בהם, צריך להוסיף הצהרת יומן ל-method initializeUIState
ב-ReplyViewModel
.
fun initializeUIState() {
Log.d("resizing-codelab", "initializeUIState() called in the viewmodel")
val mailboxes: Map<MailboxType, List<Email>> =
LocalEmailsDataProvider.allEmails.groupBy { it.mailbox }
_uiState.value =
ReplyUiState(
mailboxes = mailboxes,
currentSelectedEmail = mailboxes[MailboxType.Inbox]?.get(0)
?: LocalEmailsDataProvider.defaultEmail
)
}
עכשיו מפעילים את האפליקציה במכשיר הבדיקה ומנסים לשנות את גודל החלון של האפליקציה כמה פעמים.
כשמסתכלים על Logcat, רואים שבאפליקציה רואים ששיטת האתחול הופעלה מספר פעמים. זו יכולה להיות בעיה בעבודה שרוצים להריץ רק פעם אחת כדי לאתחל את ממשק המשתמש. שיחות רשת נוספות, קלט/פלט (I/O) או פעולות אחרות אחרות יכולות לפגוע בביצועי המכשיר ולגרום לבעיות לא מכוונות אחרות.
כדי להימנע מעבודה מיותרת ברקע, צריך להסיר את הקריאה אל initializeUIState()
מ-method onCreate()
של הפעילות. במקום זאת, צריך לאתחל את הנתונים בשיטה init
של ViewModel
. זה מבטיח ששיטת האתחול תפעל רק פעם אחת, עם יצירת המופע הראשונה של ReplyViewModel
:
init {
initializeUIState()
}
נסו להפעיל שוב את האפליקציה ומשימת האתחול המדומה המיותרת תפעל רק פעם אחת, לא משנה כמה פעמים תשנו את גודל החלון של האפליקציה. הסיבה לכך היא שמודלים של ViewModels נמשכים גם אחרי מחזור החיים של Activity
. על ידי הרצת הקוד פעם אחת בלבד כשיוצרים את ViewModel
, אנחנו מפרידים אותו משחזורים של Activity
ומונעים עבודה מיותרת. אם זו הייתה למעשה קריאת שרת יקרה או פעולת קלט/פלט גדולה של קובץ לצורך אתחול ממשק המשתמש, הייתם חוסכים משאבים משמעותיים ומשפרים את חוויית המשתמש.
7. מזל טוב!
הצלחת! כל הכבוד! יישמת עכשיו כמה שיטות מומלצות כדי לאפשר לאפליקציות ל-Android לשנות את הגודל בצורה טובה ב-ChromeOS ובסביבות אחרות עם ריבוי חלונות.
קוד מקור לדוגמה
שכפול המאגר מ-GitHub
git clone https://github.com/android/large-screen-codelabs/
...או מורידים קובץ ZIP של המאגר ומחלצים אותו