本页面介绍了如何使用现有 Glance 组件,通过 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 发布了其他组件,如下表所示
名称 | 图像 | 参考链接 | 附加说明 |
---|---|---|---|
实心按钮 |
![]() |
组件 | |
描边按钮 |
![]() |
组件 | |
图标按钮 |
![]() |
组件 | 主要 / 次要 / 仅图标 |
标题栏 |
![]() |
组件 | |
Scaffold | Scaffold 和标题栏在同一演示中。 |
有关设计细节的更多信息,请参阅 Figma 上此设计工具包中的组件设计。
有关规范微件布局的更多信息,请访问规范微件布局。