Привет! Я с разработкой виджетов никогда не пересекался, честно говоря даже забыл о них, но на работе возникла задача - изучить, рассказать и интегрировать виджеты в приложение. После того, как задача была закончена решил поделиться своим опытом. Сталкивался с трудностями, буду рад, если статья поможет их разрешить.
Что такое виджеты и почему они особенные
Android виджеты используют RemoteViews - специальный механизм для отображения UI за пределами своего процесса. RemoteViews это вью, имеющие ограниченную функциональность по сравнению с обычными View, которыми мы привыкли пользоваться. Работа с виджетами с каждой версией улучшалась и в итоге вышел новый подход к проектированию виджетов. Библиотека Glance. Она похожа на декларативную верстку через Compose, но все же стоит понимать, что это не он.
Почему glance это не compose:
Ограниченный набор компонентов
Отсутствие прямых обработчиков событий
Ограниченные модификаторы
Отсутствие рекомпозиции
Разная система тем и стилей
На самом деле различий намного больше, это только часть из них
Почему нельзя использовать обычные view:
Производительность - виджеты должны быть легковесными
Виджеты работают в процессе launcher из-за чего нет доступа к твоему приложению. RemoteViews - это механизм Android для кросс-процессного рендеринга UI
Согласованность - единый подход для всех виджетов на устройстве.
По сути Glance - это современный API-мост между декларативным стилем Compose и системными ограничениями Android виджетов, которые требуют использования RemoteViews для кросс-процессного рендеринга.
Исторический контекст
Android 1.5 (API 3) - Рождение виджетов
Первая версия виджетов была простой и функционально ограниченной:
Отображение статической информации
Базовые взаимодействия (открытие приложения)
Минимальные возможности кастомизации
Эволюция: от API 3 до Android 11
С каждой версией Android виджеты становились мощнее:
Поддержка настройки размеров - гибкая адаптация под сетки
Списки и коллекции -
ListView
,GridView
в виджетахAuto-resize - автоматическое масштабирование контента
Material Design - современные визуальные стандарты
Обновления по расписанию - фоновые работы
Android 12 (API 31) - Революция в мире виджетов
Android 12 принес кардинальные изменения:
Новый гибкий API - переработанная архитектура
Сложные анимации - плавные переходы и эффекты
Кастомные вью - расширенные возможности кастомизации
Динамические цвета - адаптация под тему системы
Улучшенная производительность - оптимизация рендеринга
Добавление виджета
Мы будем пользоваться новым API для создания виджета, поэтому сразу подключим библиотеку Glance с актуальной версией:
implementation("androidx.glance:glance-appwidget:1.1.1")
После чего у нас появятся все необходимые инструменты.
Далее необходимо сделать важные 4 пункта:
Создать наследника GlanceAppWidget
Создать наследника GlanceAppWidgetReceiver
Создать конфигурационный файл, описывающий для системы настройки виджета
Подключить это все в manifest
GlanceAppWidget
Этот класс будет отвечать за весь UI и взаимодействие с ним. Напишем простой контейнер для начала.
class AppWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceTheme {
}
}
}
}
И по сути мы можем описывать внутри GlanceTheme UI в таком же декларативном стиле, как и Compose.
GlanceAppWidgetReceiver
Этот класс отвечает за регистрацию твоего виджета в манифесте, позволяет обрабатывать системные события, связанные с твоим виджетом. Так же возвращает экземпляр твоего виджета
Почему его нужно регистрировать в Manifest.xml? Потому что он наследник BroadcastReceiver:
class AppWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = AppWidget()
}
Файл конфигурации виджета
Это глобальный файл с настройками твоего виджета, там можно много чего указать, разберем несколько свойств
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description"
android:targetCellHeight="2"
android:targetCellWidth="2"
android:resizeMode="vertical|horizontal"
android:previewLayout="@layout/widget_preview_layout"
android:previewImage="@drawable/widget_preview_image"
android:maxResizeWidth="600dp"
android:maxResizeHeight="120dp"
android:minResizeWidth="120dp"
android:minResizeHeight="40dp"
android:minHeight="120dp"
android:minWidth="120dp"
android:updatePeriodMillis="0"
android:widgetFeatures="reconfigurable" />
description - описание виджета, которое пользователь увидит в списке виджетов, когда решит его добавить
targetCellHeight/targetCellWidth - новая функциональность для Android 12 (API 31) +. Весь экран представляет собой сетку. Каждая иконка приложения это по сути виджет размера 1х1. Мы можем задать размер виджета, используя сетку
updatePeriodMillis - частота обновления виджета. В данном случае мы указываем 0, поскольку за обновление виджета в статье будет отвечать WorkManager
widgetFeatures - говорит о том, что у виджета есть возможность изменять настройки. В будущем мы добавим конфигурационную activity, в которой можно будет менять настройки виджета. Эта activity будет вызвана при первом добавлении виджета на экран, а значение reconfigurable говорит о том, что мы можем сколько угодно раз изменять конфигурацию виджета
previewLayout/previewImage - image для Android 11 (API 30) -, layout для версий выше. Это изображение, которое видит пользователь, когда находится в списке виджетов. Раньше была просто статическая картинка, теперь это полноценный xml файл, который и размер системы учтет, и dark mode режим и т.д.
Manifest
Соберем это все безобразие в один файл и подключим к нашему приложению
<receiver
android:name=".widgets.AppWidgetReceiver"
android:exported="false"
android:enabled="true"
android:label="@string/app_widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider" />
</receiver>
Я так же опустил здесь подключение конфигурационной activity, но доберемся до этого позже
UI
Теперь мы можем добавить наш виджет на экран. Но, чтобы увидеть какой-нибудь результат - добавим заглушку.
Для начала добавим data class для того, чтобы держать какие-то данные, которые мы можем отобразить. Я создал следующий класс:
data class AppWidgetDataContent(
@StringRes val titleResId: Int, val description: String, val value: Int
)
Создал из него заглушку и сверстал следующий контент:
@Composable
fun AppWidgetContent(
state: AppWidgetDataContent,
onRefreshClick: () -> Unit
) {
Column(
modifier = GlanceModifier
.fillMaxSize()
.padding(12.dp)
.background(GlanceTheme.colors.widgetBackground)
) {
AppWidgetTitleContent(state.titleResId, false, onRefreshClick)
}
}
@Composable
private fun AppWidgetTitleContent(
@StringRes titleResId: Int,
isLoading: Boolean,
onRefreshClick: () -> Unit
) {
val context = LocalContext.current
Row(
modifier = GlanceModifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = context.getString(titleResId),
modifier = GlanceModifier.defaultWeight(),
maxLines = 1,
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
color = GlanceTheme.colors.onSurface
)
)
when (isLoading) {
true -> {
CircularProgressIndicator(
modifier = GlanceModifier.size(24.dp),
color = GlanceTheme.colors.outline
)
}
false -> {
Image(
provider = ImageProvider(R.drawable.vec_refresh_primary),
contentDescription = null,
modifier = GlanceModifier.clickable { onRefreshClick() }
)
}
}
}
}
И добавил эту верстку в наш AppWidget в блок GlanceTheme. Важный нюанс верстки. Для отрисовки UI мы пользуемся библиотекой Glance. То есть импорты у нас следующие:
import androidx.glance.layout.Column
import androidx.glance.layout.Alignment
import androidx.glance.text.Text
...
Не перепутай.
Итог
Я показал как создать и добавить простой виджет для Android с использованием библиотеки Glance.
От автора
Это моя первая статья. Контента для нее оказалось слишком много и я решил разбить ее на несколько частей. Пока не знаю, сколько получится глав, но в планах показать следующее:
Добавление конфигурационной activity, в которой можно изменять настройки виджета;
Обновление виджета при помощи WorkManager. Например, у нас есть сетевой слой, там загружаются какие-то данные. Эти данные будут сохраняться в локальное хранилище, откуда виджет будет их доставать;
Взаимодействие с виджетом.
Возможно будут еще идеи и я их допишу. Часть кода со временем будет меняться, поэтому в итоговой статье я приложу ссылку на гитхаб.
Комментарии (2)
Spyman
02.09.2025 10:05Я правильно понимаю - что Glance - не более чем обертка над remoteViews и функционально ничего не прибавляет?
Helpa
@Klimov_ilya_sСпасибо за статью. А можно еще сделать репозиторий на github или где вам удобней, с проектом который описан в статье и видимо который будет продолжаться в следующих статьях.