本页面介绍如何使用现有 Glance 组件处理大小并提供灵活且响应式的布局。
使用 Box
、Column
和 Row
Glance 有三个主要的可组合布局
Box
:将元素放置在另一个元素之上。它转换为RelativeLayout
。Column
: 将元素沿垂直轴线依次排列。它等效于LinearLayout
的垂直方向排列。Row
: 将元素沿水平轴线依次排列。它等效于LinearLayout
的水平方向排列。
Glance 支持 Scaffold
对象。将您的 Column
、Row
和 Box
可组合项放置在给定的 Scaffold
对象中。
每个可组合项都允许您使用修饰符定义其内容的垂直和水平对齐方式,以及宽度、高度、权重或填充约束。此外,每个子项都可以定义自己的修饰符,以更改其在父项中的空间和位置。
以下示例演示了如何创建一个 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
有助于提高性能,并从 Android 12 开始(例如,在向列表中添加或删除项目时)保持列表和 appWidget
更新后的滚动位置。以下示例演示了如何指定 itemId
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
定义 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 // ... } }
使用此模式时,请确保
- 根据内容大小正确定义最小和最大大小的 元数据值。
- 内容在预期的尺寸范围内足够灵活。
通常情况下,当以下情况之一成立时,您应该使用此模式:
a) AppWidget
的大小固定,或者 b) 当大小调整时,它不会更改其内容。
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
等效于 提供精确布局,它每次 AppWidget
的可用大小发生变化时(例如,当用户在主屏幕上调整 AppWidget
大小时)都会请求 GlanceAppWidget
的内容。
例如,在目标小部件中,如果可用宽度大于某个值,则可以添加一个额外的按钮。
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
都必须完全重新创建。如果内容比较复杂,这会导致性能问题和 UI 抖动。 - 可用大小可能因启动器的实现而异。例如,如果启动器不提供尺寸列表,则使用最小可能的尺寸。
- 在 Android 12 之前的设备上,尺寸计算逻辑可能无法在所有情况下都正常工作。
通常情况下,如果无法使用 SizeMode.Responsive
(即,无法使用一小组响应式布局),您应该使用此模式。
访问资源
使用 LocalContext.current
访问任何 Android 资源,如以下示例所示
LocalContext.current.getString(R.string.glance_title)
我们建议直接提供资源 ID,以减小最终 RemoteViews
对象的大小,并启用动态资源,例如 动态颜色。
可组合项和方法使用“提供者”来接受资源,例如 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,用于设置文本样式。使用 TextStyle 类中的 fontSize
、fontWeight
或 fontFamily
属性设置文本样式。
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 { mutableStateOf(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) } ) } }
您还可以为 CheckBox
、Switch
和 RadioButton
提供 colors
属性,以自定义其颜色
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 包括其他组件的发布,如以下表格所述
名称 | Image | 参考链接 | 其他说明 |
---|---|---|---|
Filled Button | 组件 | ||
Outline Buttons | 组件 | ||
Icon Buttons | 组件 | Primary / Secondary / Icon-only | |
Title Bar | 组件 | ||
脚手架 | Scaffold 和 Title bar 在同一个演示中。 |
有关设计细节的更多信息,请参阅 Figma 上的 设计套件 中的组件设计。