На этой странице описывается, как обрабатывать размеры и создавать гибкие и адаптивные макеты с помощью 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.
Для получения дополнительной информации о канонических макетах посетите страницу Канонические макеты виджетов .