בדף הזה מוסבר איך לטפל בגדלים ולספק פריסות גמישות ומותאמות באמצעות Glance, תוך שימוש ברכיבי Glance קיימים.
שימוש ב-Box, ב-Column וב-Row
ל-Glance יש שלושה פריסות עיקריות שאפשר להרכיב מהן פריסות אחרות:
Box: הצבת רכיבים אחד על גבי השני. היא מתורגמת ל-RelativeLayout.
Column: ממקם את הרכיבים אחד אחרי השני בציר האנכי. הוא מתורגם ל-LinearLayoutעם כיוון אנכי.
Row: הצבת האלמנטים אחד אחרי השני בציר האופקי. הוא מתורגם ל-LinearLayoutעם כיוון אופקי.
התכונה 'בקצרה' תומכת באובייקטים של 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. רכיב ה-Composable הזה מאפשר להגדיר קבוצה של פריטים שיוצגו בתוך מאגר שניתן לגלילה בווידג'ט של האפליקציה.
קטעי הקוד הבאים מציגים דרכים שונות להגדיר פריטים בתוך התג 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") ) if (Build.VERSION.SDK_INT >= 36 && isSnapScrollSupported) { LazyColumn( verticalScrollMode = VerticalScrollMode.SnapScrollMatchHeight(height) ) { items(items) { item -> ColorCard(item, height) } } } else { LazyColumn { 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) ) } } val isSnapScrollSupported: Boolean get() = Build.VERSION.SDK_INT >= 36 && Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1
מה ההגדרה של 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.
בטבלה הבאה מוצג הערך של המאפיין size, בהתאם לערך של SizeMode ולגודל הזמין של AppWidget:
| גודל זמין | 105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
|---|---|---|---|---|
SizeMode.Single |
110x110 | 110x110 | 110x110 | 110x110 |
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 (כלומר, אם אי אפשר להשתמש בפריסות רספונסיביות).
גישה למשאבים
אפשר להשתמש ב-LocalContext.current כדי לגשת לכל משאב של Android, כמו בדוגמה הבאה:
LocalContext.current.getString(R.string.glance_title)
מומלץ לספק מזהי משאבים ישירות כדי לצמצם את הגודל של אובייקט RemoteViews הסופי וכדי להפעיל משאבים דינמיים, כמו צבעים דינמיים.
רכיבי Composables ושיטות מקבלים משאבים באמצעות 'ספק', כמו 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" )
כשהמצב משתנה, מופעלת פונקציית ה-lambda שצוינה. אפשר לאחסן את מצב הבדיקה, כמו בדוגמה הבאה:
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 כוללת את הרכיבים הנוספים שמתוארים בטבלה הבאה:
| שם | תמונה | קישור מועיל | הערות נוספות |
|---|---|---|---|
| כפתור מלא |
|
רכיב | |
| לחצנים עם קווי מתאר |
|
רכיב | |
| כפתורים עם סמלים |
|
רכיב | ראשי / משני / סמל בלבד |
| סרגל הכותרת |
|
רכיב | |
| פיגום | הפיגום וסרגל הכותרת נמצאים באותו סרטון הדגמה. |
מידע נוסף על פרטי העיצוב מופיע בעיצובים של הרכיבים בערכת העיצוב הזו ב-Figma.
מידע נוסף על פריסות קנוניות של ווידג'טים זמין במאמר פריסות קנוניות של ווידג'טים.