На этой странице описывается, как обрабатывать размеры и обеспечивать гибкость и оперативность. макеты с Glance, используя существующие компоненты Glance.
Используйте Box, Column и Row
Glance имеет три основных компонуемых макета:
Box: размещает элементы друг над другом. Это означаетRelativeLayout.Column– элементы располагаются друг за другом по вертикальной оси. Оно преобразуется вLinearLayoutс вертикальной ориентацией.Row– элементы располагаются друг за другом по горизонтальной оси. Оно преобразуется вLinearLayoutс горизонтальной ориентацией.
Glance поддерживает Scaffold объектов. Разместите composable-функции Column, Row и Box в объекте Scaffold.
Каждая из этих composable-функций позволяет определить вертикальное и горизонтальное выравнивание своего контента, а также ограничения ширины, высоты, веса или отступов с помощью модификаторов. Кроме того, каждый дочерний элемент может определить свой модификатор, чтобы изменить пространство и размещение внутри родительского элемента.
В следующем примере показано, как создать Row, который равномерно распределяет дочерние элементы по горизонтали, как показано на рисунке 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Row заполняет максимально доступную ширину, и, поскольку каждый дочерний элемент имеет одинаковый вес, они равномерно делят доступное пространство. Вы можете задавать разные веса, размеры, отступы или выравнивания, чтобы адаптировать макеты к своим потребностям.
Используйте прокручиваемые макеты
Ещё один способ обеспечить адаптивность контента – сделать его прокручиваемым. Это можно сделать с помощью composable-функции LazyColumn. Эта composable-функция позволяет определить набор элементов, которые будут отображаться в прокручиваемом контейнере виджета приложения.
В следующих фрагментах кода показаны разные способы определения элементов в 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.
Если вы используете пошаговую прокрутку с изображениями, ознакомьтесь с каноническим макетом Изображение на весь экран.
@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 и включить динамические ресурсы, например динамические цвета.
Composables-функции и методы принимают ресурсы с помощью поставщика, например 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 | Scaffold и строка заголовка находятся в одной демонстрации. |
Дополнительную информацию о дизайне компонентов можно найти в наборе инструментов в Figma.
Подробнее о канонических макетах виджетов…