На этой странице описывается, как обрабатывать размеры и создавать гибкие и адаптивные макеты с помощью 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, 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 // ... } }
При использовании этого режима убедитесь, что:
- Значения метаданных минимального и максимального размера правильно определены в зависимости от размера контента.
- Содержимое достаточно гибкое в пределах ожидаемого диапазона размеров.
В общем, вам следует использовать этот режим, когда:
а) 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 х 110 | 203 х 112 | 72 х 72 | 203 х 150 |
---|---|---|---|---|
SizeMode.Single | 110 х 110 | 110 х 110 | 110 х 110 | 110 х 110 |
SizeMode.Exact | 105 х 110 | 203 х 112 | 72 х 72 | 203 х 150 |
SizeMode.Responsive | 80 х 100 | 80 х 100 | 80 х 100 | 150 х 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 { 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" )
При изменении состояния срабатывает предоставленная лямбда. Вы можете сохранить состояние проверки, как показано в следующем примере:
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.