Android. Glance Widgets. Начало

от автора

Привет! Я с разработкой виджетов никогда не пересекался, честно говоря даже забыл о них, но на работе возникла задача — изучить, рассказать и интегрировать виджеты в приложение. После того, как задача была закончена решил поделиться своим опытом. Сталкивался с трудностями, буду рад, если статья поможет их разрешить.

Что такое виджеты и почему они особенные

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 виджеты становились мощнее:

  • Поддержка настройки размеров — гибкая адаптация под сетки

  • Списки и коллекции — ListViewGridView в виджетах

  • Auto-resize — автоматическое масштабирование контента

  • Material Design — современные визуальные стандарты

  • Обновления по расписанию — фоновые работы

Android 12 (API 31) — Революция в мире виджетов

Android 12 принес кардинальные изменения:

  • Новый гибкий API — переработанная архитектура

  • Сложные анимации — плавные переходы и эффекты

  • Кастомные вью — расширенные возможности кастомизации

  • Динамические цвета — адаптация под тему системы

  • Улучшенная производительность — оптимизация рендеринга

Добавление виджета

Мы будем пользоваться новым API для создания виджета, поэтому сразу подключим библиотеку Glance с актуальной версией:

implementation("androidx.glance:glance-appwidget:1.1.1")

После чего у нас появятся все необходимые инструменты.

Далее необходимо сделать важные 4 пункта:

  1. Создать наследника GlanceAppWidget

  2. Создать наследника GlanceAppWidgetReceiver

  3. Создать конфигурационный файл, описывающий для системы настройки виджета

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

  • Взаимодействие с виджетом.

Возможно будут еще идеи и я их допишу. Часть кода со временем будет меняться, поэтому в итоговой статье я приложу ссылку на гитхаб.


ссылка на оригинал статьи https://habr.com/ru/articles/942886/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *