На этой странице описано, как работать с размерами и создавать гибкие и адаптивные макеты с помощью 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 помогает повысить производительность и сохранить положение прокрутки при обновлениях списков и 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.
Если вы используете прокрутку изображений с привязкой к фону, см. шаблон Canonical Layout для изображений, занимающих всю ширину страницы .
@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 использовать невозможно (то есть, создание небольшого набора адаптивных макетов не представляется возможным).
Доступ к ресурсам
Используйте LocalContext.current для доступа к любому ресурсу Android, как показано в следующем примере:
LocalContext.current.getString(R.string.glance_title)
Мы рекомендуем указывать идентификаторы ресурсов напрямую, чтобы уменьшить размер итогового объекта 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 для установки стилей текста. Стили текста можно задать с помощью атрибутов 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 и Title bar находятся в одном демонстрационном ролике. |
Для получения более подробной информации о специфике проектирования см. примеры компонентов в этом наборе инструментов для проектирования на Figma.
Для получения дополнительной информации о канонических макетах посетите страницу «Канонические макеты виджетов» .


