בדף הזה מוסבר איך לטפל בגדלים ולספק פריסות גמישות ורספונסיביות באמצעות Glance, באמצעות רכיבי Glance קיימים.
שימוש ב-Box, ב-Column וב-Row
ל-Glance יש שלוש פריסות קומפוזביליות עיקריות:
Box: הצבת רכיבים אחד מעל השני. התרגום הואRelativeLayout.
Column: הצבת רכיבים אחד אחרי השני בציר האנכי. המשמעות היאLinearLayoutבכיוון אנכי.
Row: הצבת רכיבים אחד אחרי השני בציר האופקי. הוא מתורגם ל-LinearLayoutבכיוון אופקי.
אפשר להשתמש ב-Glance כדי לזהות Scaffold אובייקטים. מציבים את הרכיבים הניתנים לשילוב Column, Row ו-Box בתוך אובייקט Scaffold נתון.
כל אחד מהרכיבים האלה מאפשר לכם להגדיר את היישור האנכי והאופקי של התוכן שלו, ואת מגבלות הרוחב, הגובה, המשקל או המרווח הפנימי באמצעות מגבילים. בנוסף, כל רכיב צאצא יכול להגדיר את ה-modifier שלו כדי לשנות את המרחב והמיקום בתוך רכיב ההורה.
בדוגמה הבאה אפשר לראות איך ליצור Row שמפזר את הצאצאים שלו באופן שווה בצורה אופקית, כמו שמוצג באיור 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
ה-Row ממלא את הרוחב המקסימלי הזמין, ומכיוון שלכל צאצא יש את אותו משקל, הם חולקים את המרחב הזמין באופן שווה. אתם יכולים להגדיר משקלים, גדלים, ריפודים או יישורים שונים כדי להתאים את הפריסות לצרכים שלכם.
שימוש בפריסות עם גלילה
דרך נוספת לספק תוכן רספונסיבי היא להפוך אותו לתוכן שניתן לגלול. אפשר לעשות את זה באמצעות הפונקציה הקומפוזבילית LazyColumn. פונקציה קומפוזבילית זו מאפשרת להגדיר קבוצה של פריטים שיוצגו בתוך קונטיינר שניתן לגלול בו בווידג'ט של האפליקציה.
בקטעי הקוד הבאים מוצגות דרכים שונות להגדיר פריטים בתוך LazyColumn.
אתם יכולים לציין את מספר הפריטים:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
פריטים בודדים:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
מזינים רשימה או מערך של פריטים:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
אפשר גם להשתמש בשילוב של הדוגמאות הקודמות:
LazyColumn { item { Text("Names:") } items(peopleNameList) { name -> Text(name) } // or in case you need the index: itemsIndexed(peopleNameList) { index, person -> Text("$person at index $index") } }
שימו לב שהקטע הקודם לא מציין את itemId. הגדרת itemId עוזרת לשפר את הביצועים ולשמור על מיקום הגלילה ברשימה ובעדכוני appWidget מ-Android 12 ואילך (לדוגמה, כשמוסיפים או מסירים פריטים מהרשימה). בדוגמה הבאה מוסבר איך לציין itemId:
items( items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
גלילה מהירה
גלילה עם הצמדה היא אנימציה שמאפשרת להצמיד תוכן שניתן לגלילה לחלק העליון של מאגר הווידג'טים.
כדי להטמיע גלילה מהירה, צריך לוודא שהתנאים הבאים מתקיימים:
- מעדכנים את התלות של Glance לגרסה 1.3.0-alpha02 ואילך.
- מגדירים את
compileSdkל-37 ומעלה, כי גלילה מהירה נתמכת במכשירים עם Android מגרסה 17 ומעלה. - הגדרת
LazyColumnבאמצעותVerticalScrollMode. אם המכשיר תומך בגלילה מהירה, משתמשים ב-SnapScrollMatchHeight. אחרת, צריך להשתמש ב-Normal.
אם אתם משתמשים בגלילה עם הצמדה לתמונות, עיינו בפריסה הקנונית תמונה ללא שוליים.
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) val scrollMode = if (Build.VERSION.SDK_INT >= 37) { VerticalScrollMode.SnapScrollMatchHeight(height) } else { VerticalScrollMode.Normal } LazyColumn( verticalScrollMode = scrollMode ) { items(items) { item -> ColorCard(item, height) } } } @Composable private fun ColorCard(item: ColorItem, height: Dp) { Box( modifier = GlanceModifier .background(item.color) .fillMaxWidth() .height(height), contentAlignment = Alignment.Center ) { Text( text = item.name, modifier = GlanceModifier.background(Color.White) ) } }
הגדרת SizeMode
הגדלים של AppWidget עשויים להשתנות בהתאם למכשיר, לאפשרויות בחירה למשתמשים או למרכז האפליקציות, ולכן חשוב לספק פריסות גמישות, כפי שמתואר בדף הוספת פריסות גמישות לווידג'טים. ב-Glance זה פשוט יותר, באמצעות ההגדרה SizeMode והערך LocalSize. בקטעים הבאים מתוארים שלושת המצבים.
SizeMode.Single
SizeMode.Single הוא מצב ברירת המחדל. הוא מציין שרק סוג אחד של תוכן מסופק. כלומר, גם אם הגודל הזמין של AppWidget משתנה, גודל התוכן לא משתנה.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Single override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the minimum size or resizable // size defined in the App Widget metadata val size = LocalSize.current // ... } }
כשמשתמשים במצב הזה, חשוב לוודא ש:
- ערכי המטא-נתונים של הגודל המינימלי והמקסימלי מוגדרים בצורה נכונה בהתאם לגודל התוכן.
- התוכן גמיש מספיק בטווח הגודל הצפוי.
באופן כללי, כדאי להשתמש במצב הזה אם:
א) ל-AppWidget יש גודל קבוע, או
ב) התוכן שלו לא משתנה כשמשנים את הגודל.
SizeMode.Responsive
המצב הזה מקביל למתן פריסות רספונסיביות, שמאפשרות ל-GlanceAppWidget להגדיר קבוצה של פריסות רספונסיביות שמוגבלות לגדלים ספציפיים. לכל גודל שמוגדר, התוכן נוצר וממופה לגודל הספציפי כשיוצרים או מעדכנים את ה-AppWidget. לאחר מכן המערכת בוחרת את הגודל המתאים ביותר על סמך הגודל הזמין.
לדוגמה, ביעד AppWidget אפשר להגדיר שלושה גדלים ואת התוכן שלהם:
class MyAppWidget : GlanceAppWidget() { companion object { private val SMALL_SQUARE = DpSize(100.dp, 100.dp) private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp) private val BIG_SQUARE = DpSize(250.dp, 250.dp) } override val sizeMode = SizeMode.Responsive( setOf( SMALL_SQUARE, HORIZONTAL_RECTANGLE, BIG_SQUARE ) ) override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be one of the sizes defined above. val size = LocalSize.current Column { if (size.height >= BIG_SQUARE.height) { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) } Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width >= HORIZONTAL_RECTANGLE.width) { Button("School") } } if (size.height >= BIG_SQUARE.height) { Text(text = "provided by X") } } } }
בדוגמה הקודמת, השיטה provideContent נקראת שלוש פעמים וממופה לגודל שהוגדר.
- בקריאה הראשונה, הגודל מוערך כ-
100x100. התוכן לא כולל את הכפתור הנוסף, וגם לא את הטקסטים העליונים והתחתונים. - בקריאה השנייה, הגודל הוא
250x100. התוכן כולל את הכפתור הנוסף, אבל לא את הטקסטים העליונים והתחתונים. - בקריאה השלישית, הגודל מוערך כ-
250x250. התוכן כולל את הכפתור הנוסף ואת שני הטקסטים.
SizeMode.Responsive הוא שילוב של שני המצבים האחרים, ומאפשר לכם להגדיר תוכן רספונסיבי בתוך גבולות מוגדרים מראש. באופן כללי, המצב הזה משפר את הביצועים ומאפשר מעברים חלקים יותר כשמשנים את הגודל של AppWidget.
בטבלה הבאה מוצג ערך הגודל, בהתאם לגודל הזמין של SizeMode ושל AppWidget:
| הגודל הזמין | 105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
|---|---|---|---|---|
SizeMode.Single |
110 x 110 | 110 x 110 | 110 x 110 | 110 x 110 |
SizeMode.Exact |
105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
SizeMode.Responsive |
80 x 100 | 80 x 100 | 80 x 100 | 150 x 120 |
| * הערכים המדויקים הם למטרות הדגמה בלבד. |
SizeMode.Exact
SizeMode.Exact שווה ערך למתן פריסות מדויקות, שמבקש את התוכן של GlanceAppWidget בכל פעם שהגודל הזמין של AppWidget משתנה (לדוגמה, כשהמשתמש משנה את הגודל של AppWidget במסך הבית).
לדוגמה, בווידג'ט היעד, אפשר להוסיף כפתור נוסף אם הרוחב הזמין גדול מערך מסוים.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Exact override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the size of the AppWidget val size = LocalSize.current Column { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width > 250.dp) { Button("School") } } } } }
המצב הזה מספק יותר גמישות מהמצבים האחרים, אבל יש לו כמה מגבלות:
- צריך ליצור מחדש את ה-
AppWidgetבכל פעם שהגודל משתנה. זה עלול לגרום לבעיות בביצועים ולתנודות בממשק המשתמש כשהתוכן מורכב. - הגודל הזמין עשוי להשתנות בהתאם להטמעה של מרכז האפליקציות. לדוגמה, אם מרכז האפליקציות לא מספק את רשימת הגדלים, המערכת משתמשת בגודל המינימלי האפשרי.
- במכשירים עם גרסאות קודמות ל-Android 12, יכול להיות שהלוגיקה של חישוב הגודל לא תפעל בכל המצבים.
באופן כללי, כדאי להשתמש במצב הזה אם אי אפשר להשתמש ב-SizeMode.Responsive (כלומר, קבוצה קטנה של פריסות רספונסיביות לא מתאימה).
גישה למשאבים
כדי לגשת למשאב כלשהו של Android, משתמשים ב-LocalContext.current, כמו בדוגמה הבאה:
LocalContext.current.getString(R.string.glance_title)
מומלץ לספק מזהי משאבים ישירות כדי להקטין את האובייקט הסופי של RemoteViews ולאפשר משאבים דינמיים, כמו צבעים דינמיים.
רכיבים ושיטות של Composable מקבלים משאבים באמצעות "ספק", כמו ImageProvider, או באמצעות שיטת עומס יתר כמו GlanceModifier.background(R.color.blue). לדוגמה:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
טקסט של הכינוי
Glance 1.1.0 כולל API להגדרת סגנונות הטקסט. מגדירים סגנונות טקסט באמצעות המאפיינים fontSize, fontWeight או fontFamily של המחלקה TextStyle.
fontFamily תומך בכל גופני המערכת, כמו בדוגמה הבאה, אבל לא תומך בגופנים מותאמים אישית באפליקציות:
Text( style = TextStyle( fontWeight = FontWeight.Bold, fontSize = 18.sp, fontFamily = FontFamily.Monospace ), text = "Example Text" )
הוספת כפתורים מורכבים
כפתורים מורכבים נוספו ב-Android 12. Glance תומך בתאימות לדורות קודמים בסוגים הבאים של כפתורים מורכבים:
כל אחד מהלחצנים המורכבים האלה מציג תצוגה שניתן ללחוץ עליה, שמייצגת את המצב "מסומן".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableIntStateOf(0) } CheckBox( checked = isApplesChecked, onCheckedChange = { isApplesChecked = !isApplesChecked }, text = "Apples" ) Switch( checked = isEnabledSwitched, onCheckedChange = { isEnabledSwitched = !isEnabledSwitched }, text = "Enabled" ) RadioButton( checked = isRadioChecked == 1, onClick = { isRadioChecked = 1 }, text = "Checked" )
כשהמצב משתנה, מופעלת פונקציית הלמבדה שסופקה. אפשר לשמור את המצב המסומן, כמו בדוגמה הבאה:
class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val myRepository = MyRepository.getInstance() provideContent { val scope = rememberCoroutineScope() val saveApple: (Boolean) -> Unit = { scope.launch { myRepository.saveApple(it) } } MyContent(saveApple) } } @Composable private fun MyContent(saveApple: (Boolean) -> Unit) { var isAppleChecked by remember { mutableStateOf(false) } Button( text = "Save", onClick = { saveApple(isAppleChecked) } ) } }
אפשר גם להוסיף את המאפיין colors לרכיבים CheckBox, Switch ו-RadioButton כדי להתאים אישית את הצבעים שלהם:
CheckBox( // ... colors = CheckboxDefaults.colors( checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight), uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked } ) Switch( // ... colors = SwitchDefaults.colors( checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan), uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta), checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow), uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked }, text = "Enabled" ) RadioButton( // ... colors = RadioButtonDefaults.colors( checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow), uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue) ), )
רכיבים נוספים
בגרסה Glance 1.1.0 יש רכיבים נוספים, כמו שמתואר בטבלה הבאה:
| שם | תמונה | קישור מפנה | הערות נוספות |
|---|---|---|---|
| כפתור מלא |
|
רכיב | |
| כפתורים עם מתאר |
|
רכיב | |
| כפתורים של סמלים |
|
רכיב | ראשי / משני / סמל בלבד |
| שורת הכותרת |
|
רכיב | |
| פיגום | Scaffold וסרגל הכותרת נמצאים באותו הדגמה. |
למידע נוסף על פרטי העיצוב, עיינו בעיצובי הרכיבים בערכת העיצוב הזו ב-Figma.
מידע נוסף על פריסות קנוניות זמין במאמר פריסות קנוניות של ווידג'טים.