Android home screen widgets

Android (Home screen) Widgets — Tutorial

Developing Android Widgets. This article describes how to create home screen widgets in Android.

1. Prerequisites

The following description assume that you already have experience in building standard Android application. Please see https://www.vogella.com/tutorials/Android/article.html — Android Tutorial. It also partly uses Android services. You find an introduction into Android Services in https://www.vogella.com/tutorials/AndroidServices/article.html — Android Service Tutorial.

2. Android Widgets

2.1. Overview about AppWidgets

2.2. Home screen widgets

Home screen widgets are broadcast receivers which provide interactive components. They are primarily used on the Android home screen. They typically display some kind of data and allow the user to perform actions with them. For example, a widget can display a short summary of new emails and if the user selects an email, it could start the email application with the selected email.

To avoid confusion with views (which are also called widgets), this text uses the term home screen widgets, if it speaks about widgets.

A widget runs as part of the process of its host. This requires that the widget preserves the permissions of their application.

Widget use RemoteViews to create their user interface. A RemoteView can be executed by another process with the same permissions as the original application. This way the widget runs with the permissions of its defining application.

The user interface for a Widget is defined by a broadcast receiver. This receiver inflates its layout into an object of type RemoteViews . This object is delivered to Android, which hands it over the home screen application.

2.3. Steps to create a Widget

To create a widget, you:

Define a layout file

Create an XML file ( AppWidgetProviderInfo ) which describes the properties of the widget, e.g. size or the fixed update frequency.

Create a BroadcastReceiver which is used to build the user interface of the widget.

Enter the Widget configuration in the AndroidManifest.xml file.

Optional you can specify a configuration activity which is called once a new instance of the widget is added to the widget host.

2.4. Widget size

Before Android 3.1 a widget always took a fixed amount of cells on the home screen. A cell is usually used to display the icon of one application. As a calculation rule you should define the size of the widget with the formula: ((Number of columns / rows) * 74) — 2 . These are device independent pixels and the -2 is used to avoid rounding errors.

As of Android 3.1 a widget can be flexible in size, e.g., the user can make it larger or smaller. To enable this for widget, you can use the android:resizeMode=»horizontal|vertical» attribute in the XML configuration file for the widget.

3. Creating the Broadcast receiver for the widget

3.1. Create and configure widget

To register a widget, you create a broadcast receiver with an intent filter for the android.appwidget.action.APPWIDGET_UPDATE action.

The receiver can get a label and icon assigned. These are used in the list of available widgets in the Android launcher.

You also specify the meta-data for the widget via the android:name=»android.appwidget.provider attribute. The configuration file referred by this metadata contains the configuration settings for the widget. It contains, for example, the update interface, the size and the initial layout of the widget.

3.2. Available views and layouts

A widget is restricted in the View classes it can use. As layouts you can use the FrameLayout , LinearLayout and RelativeLayout classes. As views you can use AnalogClock , Button , Chromometer , ImageButton , ImageView , ProgressBar and TextView .

As of Android 3.0 more views are available: GridView , ListView , StackView , ViewFlipper and AdapterViewFlipper . These adapter views require that you define a collection view widget which is described later in this tutorial.

The only interaction that is possible with the views of a widget is via an OnClickListener event. This OnClickListener can be registered on a widget and is triggered by the user.

3.3. AppWidgetProvider

Your BroadcastReceiver implementation typically extends the AppWidgetProvider class.

The AppWidgetProvider class implements the onReceive() method, extracts the required information and calls the following widget life cycle methods.

As you can add several instances of a widget to the home screen, you have life cycle methods which are called only for the first instance added / removed to the home screen and others which are called for every instance of your widget.

Table 1. Life cycle method

Called the first time an instance of your widget is added to the home screen.

Called once the last instance of your widget is removed from the home screen.

Called for every update of the widget. Contains the ids of appWidgetIds for which an update is needed. Note that this may be all of the AppWidget instances for this provider, or just a subset of them, as stated in the method’s JavaDoc. For example, if more than one widget is added to the home screen, only the last one changes (until reinstall).

Widget instance is removed from the home screen.

3.4. Receiver and asynchronous processing

A widget has the same runtime restrictions as a normal broadcast receiver, i.e., it has only 5 seconds to finish its processing.

A receive (widget) should therefore perform time consuming operations in a service and perform the update of the widgets from the service.

4. Widget updates

A widget gets its data on a periodic timetable. There are two methods to update a widget, one is based on an XML configuration file and the other is based on the Android AlarmManager service.

In the widget configuration file you can specify a fixed update interval. The system will wake up after this time interval and call your broadcast receiver to update the widget. The smallest update interval is 1800000 milliseconds (30 minutes).

The AlarmManager allows you to be more resource efficient and to have a higher frequency of updates. To use this approach, you define a service and schedule this service via the AlarmManager regularly. This service updates the widget.

Please note that a higher update frequency will wake up the phone from the energy safe mode. As a result your widget consumes more energy.

5. Exercise: widget with fixed update interval

5.1. Target

In the following tutorial you create a widget which displays a random number. This random number is updated every 30 minutes. You also register an OnClickListener so that the widgets updates once the user clicks on it.

The resulting widget will look like the following.

5.2. Create project and widget implementation

Create a new Android project called de.vogella.android.widget.example with an activity in the package de.vogella.android.widget.example .

Create a new file myshape.xml in the /res/drawable_ folder. This file defines the drawable used as background in the widget.

Define the following widget_layout.xml file under the res/layout_ folder.

Create a new resource file called widget_info.xml via right click on the res folder and by selecting New Android resource file .

Create the following receiver class which is called during updates.

Open the AndroidManifest.xml and register your widget similar to the following listing.

This attribute specifies that the AppWidgetProvider accepts the ACTION_APPWIDGET_UPDATE broadcast and specifies the metadata for the widget.

5.3. Validate

Deploy your application on your Android device. Once your application has been deployed use the Android launcher to install your new widget on the home screen and test it.

6. Collection View Widgets

Collection view widgets add support for the usage of the ListView , StackView and GridView classes in widgets.

For collection view widgets you need two layout files, one for the widget and one for each item in the widget collection.

The widget items are filled by an instance of the RemoteViewsFactory factory class.

This factory class is provided by an Android service which extends the RemoteViewsService class. This service requires the android.permission.BIND_REMOTEVIEWS permission.

To connect your views with the service, you use your onUpdate() method in your widget implementation.

You define an intent pointing to the service and use the setRemoteAdapter method on the RemoteViews class.

7. Enabling a app widget for the lock Screen

Since Android 4.2, it is possible to add home screen app widgets to the lock screen of an Android device. To enable your widget for the look screen you need to add keyguard category in the android:widgetCategory attribute in the AppWidgetProviderInfo XML file. The following code shows an example.

In this example you declare a widget to support both — the home and the lock screens. If you recompile and launch your application now, you will be able to add the widget to the lock screen already.

You can also detect a widget category at runtime. For this, in the AppWidgetProvider.onUpdate() method, you can check for the category option of a widget with the following code.

Using this technique you can decide at runtime whether the widgets your application provides, will look differently, when they are hosted on the lock screen.

Similarly to how you used the android:initialLayout attribute for defining an initial layout for home screen widgets, you can use a new android:initialKeyguardLayout attribute for the lock screen in the AppWidgetProviderInfo XML file. This layout will appear immediately after a widget is added and will be replaced by the real layout once the widget is initialized.

8. Exercise: Update widget via a service

The following will demonstrate the usage of a service to update the widget.

Create the following UpdateWidgetService class in your project.

Add this class as a Service to your AndroidManifest.xml file.

Change MyWidgetProvider to the following. It will now only construct the service and start it.

Once called, this service will update all widgets. You can click on one of the widgets to update all widgets.

Источник

Виджеты на Android. Редкая фича, в которой придется разобраться

Привет, Хабр! Меня зовут Александр Хакимов, я android-разработчик в компании FINCH.

У вас бывало такое, что ваш дизайн был под iOS, а вам приходится адаптировать его под android? Если да, то часто ли ваши дизайнеры используют виджеты? К сожалению, виджет — редкий кейс для многих разработчиков, потому что с ним редко кто работает,

В статье я подробно расскажу как создать виджет, на что стоит обратить внимание и поделюсь своим кейсом.

Создание виджета

Для создания виджета нужно знать:

  1. Особенности компонентов виджета.
  2. Особенности отображения виджета в сетке экрана.
  3. Особенности обновления виджета.

Разберем каждый пункт отдельно.

Особенности компонентов виджета

С этим пунктом знаком любой разработчик, который хоть раз работал с RemoteViews. Если вы из таких, смело переходите к следующему пункту.

RemoteViews предназначен для описания и управления иерархиями Views, которые принадлежат процессу другого приложения. С помощью управления иерархиями можно изменять свойства или вызывать методы, принадлежащие View, которое выступает частью другого приложения. В RemoteViews входит ограниченный набор компонентов стандартной библиотеки компонентов android.widget.

View внутри виджетов работают в отдельном процессе (как правило, это домашний экран), поэтому для изменения UI виджета используется расширение BroadcastReceiver — AppWidgetProvider, работающий в нашем приложении.

Особенности отображения виджета в «сетке» экрана

Each widget must define a minWidth and minHeight, indicating the minimum amount of space it should consume by default. When users add a widget to their Home screen, it will generally occupy more than the minimum width and height you specify. Android Home screens offer users a grid of available spaces into which they can place widgets and icons. This grid can vary by a device; for example, many handsets offer a 4×4 grid, and tablets can offer a larger, 8×7 grid.

Переводя на русский: каждый виджет должен задать свои минимальную ширину и высоту, чтобы обозначить минимальное пространство, которое будет им занято по умолчанию.


Пример настроек виджета при создании в Android Studio

Виджет, который добавили на на Home screen, обычно займет больше места чем минимальные ширина и высота экрана, которые вы задали. Android Home screens предоставляет пользователям сетку доступного пространств, в которых могут быть расположены виджеты и иконки. Эта сетка может отличаться в зависимости от устройства; например, многие телефоны предлагают сетку 4х4, а планшеты могут предложить большие сетки 8х7.

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

Минимальные ширину и высоту виджета для заданного количества столбцов и строк можно вычислить по формуле:

minSideSizeDp = 70 × n − 30, где n —количество строк или столбцов

На текущий момент максимальный минимум сетки, которую вы можете задать это 4х4. Это гарантирует, что ваш виджет будет отображаться на всех девайсах.

Особенности обновления виджета

Так как AppWidgetProvider по своей сути является расширением BroadcastReceiver-а, с ним можно делать все то же самое, что и с обычным BroadcastReceiver. AppWidgetProvider просто парсит соответствующие поля из Intent, полученного в onReceive и вызывает методы перехвата с полученными extras.

Сложность возникла с частотой обновления контента — все дело в разнице внутренней работы виджетов на iOS и Android. Дело в том, что данные на iOS-виджетах обновляются тогда, когда виджет становится виден пользователю. В Android, такого события не существует. Мы не можем узнать, когда пользователь видит виджет.

Для виджетов на Android рекомендуемым способом обновления является обновление по таймеру. Настройки таймера задаются параметром виджета updatePeriodMillis. К сожалению, эта настройка не позволяет обновлять виджет чаще чем раз в 30 минут. Ниже я расскажу об этом подробнее.

Кейс создания виджета

Дальше речь пойдет о кейсе который был у нас в FINCH в крупном лотерейном приложении с приложением «Столото» для участия в государственных лотереях.

Задача приложения – упростить и сделать прозрачным для пользователя выбор лотереи и покупку билета. Поэтому требуемая функциональность виджета довольно проста: показывать пользователю рекомендуемые игры для покупки и по тапу переходить к соответствующей. Список игр определяется на сервере и регулярно обновляется.

В нашем кейсе дизайн виджета предусматривал два состояния:

  • Для авторизованного пользователя
  • Для неавторизованного пользователя

Авторизованному пользователю нужно показывать данные его профиля: состояние внутреннего кошелька, количество билетов ожидающих розыгрыша и сумму неполученных выигрышей. Для каждого из этих элементов предусмотрен, отличный от других, переход на экран внутри приложения.

Как вы могли заметить, еще одной особенностью для авторизованного пользователя является кнопка «обновить», но об этом позже.

Для реализации отображения двух состояний, с учетом дизайна, я использовал RemoteAdapter в виде реализации RemoteViewsService, чтобы генерировать карточки контента.

А теперь немного кода и того, как все работает внутри. Если у вас уже был опыт работы с виджетом, то вы знаете, что любое обновление данных виджета начинается с метода onUpdate:

Пишем апдейт для каждого инстанса нашего виджета.

Пишем реализацию нашего сервиса. В нем нам важно указать, какую реализацию интерфейса RemoteViewsService.RemoteViewsFactory использовать, чтобы генерировать контент.

Фактически это тонкий wrapper над Adapter. Благодаря ему, мы можем связывать наши данные с remote collection view. RemoteViewsFactory предоставляет методы генерации RemoteViews для каждого элемента в наборе данных. У конструктора нет никаких требований — все что я делаю, это передаю в нем контекст.

Далее будет пару слов об основных методах:

  1. onCreate – создание адаптера.
  2. getLoadingView – метод предлагает возвращать View, которое система будет показывать вместо пунктов списка, пока они создаются. Если ничего здесь не создавать, то система использует некое дефолтное View.
  3. getViewAt – метод предлагает создать пункты списка. Здесь идет стандартное использование RemoteViews.
  4. onDataSetChanged вызывается, когда поступил запрос на обновление данных в списке. Т.е. в этом методе мы подготавливаем данные для списка. Метод заточен под выполнение тяжелого, долгого кода.
  5. onDestroy вызывается при удалении последнего списка, который использовал адаптер (один адаптер может использоваться несколькими списками).
  6. RemoteViewsFactory живет пока все инстансы списка живы, поэтому мы можем хранить в нем текущие данные, например, список текущих айтемов.

Определяем список данных, который будем показывать:

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

При вызове команды на обновление данных, так же вызываем updateDataSync()

Внутри updateDataSync тоже все просто. Очищаем текущий список item-ов. Загружаем данные профиля и игры.

Здесь уже поинтереснее

Так как нам важно показывать профиль только авторизованному пользователю, то и информацию профиля нам нужно загружать только в этом случае:

Модель WidgetProfile собирается из разных источников, поэтому логика их получения и её дефолтные значения устроены таким образом, что отрицательное значение кошелька говорит о некорректных данных или проблемах с их получением.

Для бизнес логики отсутствие данных кошелька является критичным, поэтому, в случае некорректного кошелька, модель профиля не будет создана и добавлена в список item-ов.

Метод updateGamesSync() использует getWidgetGamesInteractor и добавляет в список widgetItems набор актуальных для виджета игр.

Прежде чем перейти к генерации карточек, рассмотрим подробнее модель WidgetItem. Она реализована через kotlin sealed class, что делает модель более гибкой, а работу с ней более удобной.

Создаем RemoteViews и определяем их отклик через FillInIntent

Метод setOnClickFillInIntent назначает указанной viewId intent, который будет объединен с родительским PendingIntent для определения поведения при клике на view с этим viewId. Таким образом, мы сможем реагировать на клики пользователей в нашем WidgetProvider.

Ручное обновление виджета

Для нашего виджета было установлено время обновления в полчаса. Можно обновлять его чаще, например, через танцы с WorkManager, но зачем грузить вашу сеть и аккумулятор? Такое поведение на первых этапах разработки казалось адекватным.

Все изменилось когда «бизнес» обратил внимание, что когда пользователь смотрит на виджет, на нем отображаются неактуальные данные: «Вот на моем iPhone, я открываю виджет и там САМЫЕ свежие данные моего профиля».

Ситуация банальна: iOS генерирует новые карточки при КАЖДОМ показе виджетов, ведь для этого у них отведен специальный экран, а Android не имеет подобных событий для виджета в принципе. Пришлось учесть, что некоторые лотереи проводятся раз в 15 минут, поэтому виджет должен давать актуальную информацию – ты хочешь поучаствовать в каком-то тираже, а он уже прошел.

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

Добавляем эту кнопку в макет layout-a со списком и инициализируем её поведение при вызове updateWidget

Первые наработки показали грустную картину: от нажатия на кнопку «обновить» до фактического обновления, могло пройти несколько секунд. Хотя виджет и генерируется нашим приложением, он фактически находится во власти системы и общается с нашим приложением через broadcast-ы.

Т.е. при нажатии на кнопку «обновить» нашего виджета запускается цепочка:

  1. Получить Intent в onReceive провайдера action’ .
  2. AppWidgetManager.ACTION_APPWIDGET_UPDATE.
  3. Вызов onUpdate для всех указанных в intent-e widgetIds.
  4. Зайти в сеть за новыми данными.
  5. Обновить локальные данные и отобразить новые карточки списка.

В результате, обновление виджета выглядело не очень красиво, так как нажав на кнопку, мы пару секунд смотрели на тот же виджет. Было непонятно обновились ли данные. Как решить проблему визуального отклика?

Во-первых, я добавил флаг isWidgetLoading с глобальным доступом через интерактор. Роль этого параметра довольно проста — не показывать кнопку «обновить», пока идет загрузка данных виджета.

Во вторых, процесс загрузки данных в фабрике я разделил на три этапа:

START — начало загрузки. На этом этапе состояние всех вьюшек адаптера и глобального флага загрузки меняется на «загружается».

MIDDLE — этап основной загрузки данных. После их загрузки глобальный флаг загрузки переводится в состояние «загружено», а в адаптере отображаются загруженные данные.

END — конец загрузки. Адаптеру на этом шаге не требуется изменять данные адаптера. Этот шаг нужен чтобы корректно обработать этап обновления вьюшек в WidgetProvider.

Давайте посмотрим подробнее как теперь выглядит обновление кнопки в провайдере:

А теперь посмотрим на то, что происходит в адаптере:

  1. В конце этапов START и MIDDLE я вызываю метод updateWidgets для того, чтобы обновить состояние view управляемых провайдером.
  2. После выполнения шага START для пользователя визуально отображается «загрузка» в ячейках виджета, и начнется этап MIDDLE.
  3. Перед тем как вызвать обновление данных адаптера на шаге MIDDLE, провайдер скроет кнопку «обновить».
  4. После выполнения шага MIDDLE, для пользователя будет отображаются новые данные и начнется этап END.
  5. Перед тем как вызвать обновление данных адаптера, на шаге END, провайдер скроет кнопку «обновить». С точки зрения фабрики все данные будут актуальными, поэтому на шаге END меняем значение loadingStep на START.

С помощью подобной реализации я достиг компромисс между требованием «бизнеса» видеть на виджете актуальные данные и необходимостью «дергать» обновление слишком часто.

Надеюсь, что статья была для вас полезной. Если у вас был опыт создания виджетов для Android, то расскажите об этом в комментариях.

Источник

Читайте также:  Ios and android app developer
Оцените статью
Method Description