Все виджеты android studio

Полный список

— создаем простой виджет
— разбираемся в его Lifecycle

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

Для понимания темы виджетов желательно знать, что такое BroadcastReceiver. Я о нем упоминал в Уроках 96 и 100. Это просто слушатель, который регистрируется в системе, ловит с помощью настроенного Intent Filter сообщения (Intent) и выполняет какой-либо код. Почему-то не сделал я отдельного урока по нему. Но, думаю, еще сделаю. Есть там свои интересные особенности, о которых можно поговорить.

Думаю, нет особой необходимости подробно объяснять, что такое виджеты. Все их видели на своих девайсах. Это что-то типа мини-приложений расположенных на рабочем столе (Home). Они позволяют просмотреть какую-либо информацию из основных приложений, либо повлиять на поведение этих приложений. В качестве примера можно привести – прогноз погоды, текущее время, баланс какого-либо счета, список сообщений в различных мессенджерах, управление состоянием WiFi/3G/GPS/Bluetooth, яркость экрана и т.д. и т.п. В этом уроке сделаем простейший виджет, который отобразит статичный текст.

Чтобы создать простейший виджет нам понадобятся три детали:

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

2) XML-файл c метаданными

В нем задаются различные характеристики виджета. Мы пока что укажем следующие параметры:

— layout-файл (из п.1.), чтобы виджет знал, как он будет выглядеть
— размер виджета, чтобы виджет знал, сколько места он должен занять на экране
— интервал обновления, чтобы система знала, как часто ей надо будет обновлять виджет

3) Класс, наследующий AppWidgetProvider. В этом классе нам надо будет реализовать Lifecycle методы виджета.

Давайте создадим три этих детали. Activity нам не понадобится, поэтому не забудьте убрать галку Create Activity в визарде создания нового проекта

Создадим проект без Activity:

Project name: P1171_SimpleWidget
Build Target: Android 2.3.3
Application name: SimpleWidget
Package name: ru.startandroid.develop.p1171simplewidget

Добавим строки в strings.xml:

Создаем layout-файл widget.xml:

RelativeLayout, а внутри зеленый TextView с текстом по центру. Т.е. виджет просто будет показывать текст на зеленом фоне.

Создаем файл метаданных res/xml/widget_metadata.xml:

В атрибуте initialLayout указываем layout-файл для виджета.

Атрибуты minHeight и minWidth содержат минимальные размеры виджета по высоте и ширине.

Есть определенный алгоритм расчета этих цифр. Как вы наверняка замечали, при размещении виджета, экран делится на ячейки, и виджет занимает одну или несколько из этих ячеек по ширине и высоте. Чтобы конвертнуть ячейки в dp, используется формула 70 * n – 30, где n – это количество ячеек. Т.е. если мы, например, хотим, чтобы наш виджет занимал 2 ячейки в ширину и 1 в высоту, мы высчитываем ширину = 70 * 2 – 30 = 110 и высоту = 70 * 1 – 30 = 40. Эти полученные значения и будем использовать в атрибутах minWidth и minHeight.

Атрибут updatePeriodMillis содержит количество миллисекунд. Это интервал обновления виджета. Насколько я понял хелп, указать мы тут можем хоть 5 секунд, но чаще, чем раз в 30 минут (1 800 000) виджет обновляться все равно не будет — это системное ограничение. Давайте пока что поставим интервал 40 минут (2 400 000). В следующих уроках мы разберемся, как самим обновлять виджет с необходимым интервалом.

Осталось создать класс, наследующий AppWidgetProvider.

onEnabled вызывается системой при создании первого экземпляра виджета (мы ведь можем добавить в Home несколько экземпляров одного и того же виджета).

onUpdate вызывается при обновлении виджета. На вход, кроме контекста, метод получает объект AppWidgetManager и список ID экземпляров виджетов, которые обновляются. Именно этот метод обычно содержит код, который обновляет содержимое виджета. Для этого нам нужен будет AppWidgetManager, который мы получаем на вход.

onDeleted вызывается при удалении каждого экземпляра виджета. На вход, кроме контекста, метод получает список ID экземпляров виджетов, которые удаляются.

onDisabled вызывается при удалении последнего экземпляра виджета.

Во всех методах выводим в лог одноименный текст и список ID для onUpdate и onDeleted.

Повторюсь — в onUpdate мы, по идее, должны накодить какое-то обновление виджета. Т.е. если наш виджет отображает, например, текущее время, то при очередном обновлении (вызове onUpdate) надо получать текущее время и передавать эту инфу в виджет для отображения. В этом уроке мы пока не будем с этим возиться.

Осталось немного подрисовать манифест. Добавьте туда ваш класс как Receiver

— укажите для него свои label и icon. Этот текст и эту иконку вы увидите в списке выбираемых виджетов, когда будете добавлять виджет на экран.

— настройте для него фильтр с action = android.appwidget.action.APPWIDGET_UPDATE

— добавьте метаданные с именем android.appwidget.provider и указанием файла метаданных xml/widget_metadata.xml в качестве ресурса

После этого секция receiver в манифесте должна получиться примерно такая:

Виджет готов. Все сохраняем и запускаем. Никаких Activity, разумеется, не всплывет. В консоли должен появиться текст:

Читайте также:  The blockheads для андроид

\P1171_SimpleWidget\bin\P1171_SimpleWidget.apk installed on device
Done!

Открываем диалог создания виджета и видим в списке наш виджет с иконкой и текстом, которые мы указывали в манифесте для receiver.

Выбираем его и добавляем на экран.

Виджет появился, смотрим логи.

onEnabled
onUpdate [8]

Сработал onEnabled, т.к. мы добавили первый экземпляр виджета. И сразу после добавления, сработал метод onUpdate для этого экземпляра. Видим, что ему назначен (У вас, скорее всего, будет другой ID). Т.е. система добавила экземпляр виджета на экран и вызвала метод обновления с указанием ID экземпляра.

Добавим еще один экземпляр

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

Теперь давайте удалим с экрана два этих экземпляра виджета. Сначала второй. В логах увидим:

Сработал onDeleted и получил на вход ID удаляемого экземпляра виджета.

Удаляем первый экземпляр. В логах:

onDeleted [8]
onDisabled

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

Наш виджет обновляется (получает вызов метода onUpdate) раз в 40 минут. Если кому не лень, добавьте снова пару виджетов на экран и подождите. Когда они снова обновятся, в логах это отразится.

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

BroadcastReceiver

Класс AppWidgetProvider является расширением класса BroadcastReceiver (в манифесте мы его и прописали как Receiver). Он просто получает от системы сообщение в onReceive, определяет по значениям из Intent, какое именно событие произошло (добавление, удаление или обновление виджета), и вызывает соответствующий метод (onEnabled, onUpdate и пр.).

В манифесте мы для нашего Receiver-класса настроили фильтр с action, который ловит события update. Каким же образом этот Receiver ловит остальные события (например, удаление)? Хелп пишет об этом так:

Т.е. ACTION_APPWIDGET_UPDATE – это единственный action, который необходимо прописать явно. Остальные события AppWidgetManager каким-то образом сам доставит до нашего AppWidgetProvider-наследника.

Отступы

Если мы расположим рядом несколько экземпляров виджета, увидим следующую картину

Не очень приятное зрелище. Надо бы сделать отступ.

Добавим android:padding=»8dp» к RelativeLayout в нашем layout-файле

Виджеты на экране поменялись автоматически и теперь выглядят более пристойно.

Кстати, для них сработал onUpdate, смотрите логи. В метод был передан массив ID всех работающих экземпляров виджета.

В Android 4.0 (API Level 14) и выше этот недостаток с отступами был устранен, и вручную делать отступы больше не надо. Давайте проверим. Уберите ранее добавленный в RelativeLayout отступ. И укажите в манифесте android:targetSdkVersion версию 14 (или выше), чтобы система знала, что можно использовать стандартные возможности, а не режим совместимости.

Все сохраняем, запускаем наш виджет на эмуляторе с 4.1. Добавим три экземпляра.

Система сама делает отступы между виджетами.

Получается для версий, ниже 4 надо делать отступ в layout, а для старших версий не надо. Хелп дает подсказку, как сделать так, чтобы ваш виджет корректно работал на всех версиях. Для этого используются квалификаторы версий.

В layout для RelativeLayuot указываете:

И создаете два файла.

res/values/dimens.xml с записью:

и res/values-v14/dimens.xml с записью:

В манифесте android:targetSdkVersion должен быть 14 или выше.

Таким образом, на старых версиях (без системного отступа) отступ будет 8dp, а на новых – 0dp и останется только системный отступ.

На следующем уроке:

— настраиваем виджет при размещении
— работаем с view-компонентами виджета при обновлении

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Полный список

— создаем виджет со списком

В третьей версии Андроид у виджетов появилась возможность работать с наборами данных типа списка или грида. Рассмотрим эту технологию на примере списка. В качестве view-компонента используется обычный ListView. Для межпроцессной работы с ним используется, как обычно в виджетах, RemoteViews. Но для заполнения нам придется создать два класса в дополнение к стандартному классу провайдера.

Первый – этот класс будет наполнять наш список значениями. Класс является реализацией интерфейса RemoteViewsService.RemoteViewsFactory, и его методы очень схожи с методами стандартного адаптера. Его обычно везде называют factory. Я же в этом уроке буду называть его просто адаптером.

Второй – класс сервиса, наследующий RemoteViewsService. В нем мы реализуем только один метод, который будет создавать и возвращать экземпляр (первого) класса, который будет заполнять список.

При создании и работе со списком в виджете необходимо понимать, как реализованы два момента: заполнение данными и реакция на нажатия.

Читайте также:  Сравнение планшетов android планшетов

Опишу вкратце схему заполнения данными. При подготовке виджета в классе провайдера мы для списка присваиваем Intent, который содержит данные для вызова нашего второго класса-сервиса. Когда система хочет обновить данные в списке (в виджете) она достает этот интент, биндится к указанному сервису и берет у него адаптер. И этот адаптер уже используется для наполнения и формирования пунктов списка.

Теперь о реализации нажатий на пункты списка. В обычном виджете использовались PendingIntent. Здесь чуть по-другому. Для каждого пункта в списке НЕ создается свой отдельный PendingIntent. Вместо этого списку дается общий, шаблонный PendingIntent. А для каждого пункта списка мы указываем отдельный Intent с extra-данными. Далее, при создании, каждому пункту списка система присваивает обработчик нажатия, который при срабатывании берет этот общий PendingIntent, добавляет к нему данные из персонального Intent, и отправляет по назначению сформированный таким образом PendingIntent. Т.е. в итоге по клику все равно срабатывает PendingIntent.

Сделаем пример и рассмотрим на практике все эти теоретические выкладки.

Создадим проект без Activity:

Project name: P1211_ListWidget
Build Target: Android 4.1
Application name: ListWidget
Package name: ru.startandroid.develop.p1211listwidget

Создаем layout-виджета — widget.xml:

Текст будет использован для отображения время обновления. Он же собственно и будет кнопкой обновления. В списке будем показывать данные.

Теперь layout строки списка – item.xml:

В каждом пункте списка у нас будет только текст.

Создаем класс-адаптер — MyFactory.java:

Методы очень похожи на методы обычного адаптера. Обсудим некоторые.

MyFactory – конструктор. Здесь никаких требований. Я, например, использую конструктор с двумя параметрами – Context и Intent. Этот Intent будет передавать нам сервис при создании адаптера. В нем я передаю адаптеру ID виджета.

getLoadingView – здесь вам предлагается возвращать View, которое система будет показывать вместо пунктов списка, пока они создаются. Если ничего здесь не создавать, то система использует некое дефолтное View.

getViewAt – создание пунктов списка. Здесь идет стандартное использование RemoteViews

onDataSetChanged – вызывается, когда поступил запрос на обновление данных в списке. Т.е. в этом методе мы подготавливаем данные для списка. Метод заточен под выполнение тяжелого долгого кода. В трех первых пунктах списка мы выводим текущее время, хэш-код адаптера и ID-виджета. Позже станет понятно, зачем.

onDestroy – вызывается при удалении последнего списка, который использовал адаптер (один адаптер может использоваться несколькими списками).

Создаем сервис – MyService.java:

В нем мы просто реализуем метод onGetViewFactory, который создает адаптер, передает ему Context и Intent, и возвращает этот созданный адаптер системе.

Класс провайдер – MyProvider.java:

onUpdate вызывается, когда поступает запрос на обновление виджетов. В нем мы перебираем ID, и для каждого вызываем метод updateWidget.

updateWidget – здесь вызываем три метода для формирования виджета и затем метод updateAppWidget, чтобы применить все изменения к виджету.

setUpdateTV – в этом методе работаем с TextView (который над списком). Ставим ему время в качестве текста и вешаем обновление виджета по нажатию.

setList – с помощью метода setRemoteAdapter указываем списку, что для получения адаптера ему надо будет обратиться к нашему сервису MyService.

Также обратите внимание, что в Intent мы помещаем ID виджета. Зачем? Этот Intent будет передан в метод сервиса onGetViewFactory. Этот метод мы реализовывали, в нем мы создаем адаптер и передаем ему тот же Intent. А уже в адаптере достаем этот ID и используем (третья строка в списке). Т.е. этот Intent пройдет через сервис и попадет в адаптер, поэтому если хотите что-то передать адаптеру, используйте этот Intent.

Но, повторюсь, это вовсе необязательно. Вы можете создать конструктор адаптера и без Intent-а на вход. Просто мне надо было как-то передать адаптеру ID виджета, поэтому я использую Intent.

setListClick – пока пустой. Чуть позже будем кодить в нем обработку нажатий на пункты списка.

Файл метаданных виджета — res/xml/widget_metadata.xml:

Фрагмент манифеста, описывающий сервис и бродкаст:

Для сервиса, необходимо установить разрешение BIND_REMOTEVIEWS. Это мы не наделяем сервис полномочиями, а наоборот, указываем, что этими полномочиями должен быть наделен тот, кто будет этот сервис вызывать. Система имеет такие полномочия, поэтому сможет использовать сервис для заполнения списка в адаптере.

Непростая это штука – виджеты, правда? Столько телодвижений из-за простого списка 🙂

Все сохраняем, инсталлим виджет. Добавим на экран. В списке он будет называться ListWidget.

Видим время обновления виджета, время формирования данных в списке, хэш-код адаптера, ID виджета.

Жмем зеленую зону для обновления.

Время обновления виджета поменялось, а вот список не обновился, время формирования данных осталось прежним.

Чтобы обновить данные в списке виджета, необходимо явно вызвать метод notifyAppWidgetViewDataChanged и передать ему ID виджета и ID списка.

Давайте сделаем это. Перепишем updateWidget в MyProvider.java:

Добавили вызов обновления данных списка.

Сохраняем, инсталлим. Теперь нажатие на зеленую зону будет обновлять и список.

Теперь давайте добавим второй виджет. У них внезапно совпадают ID виджета и хэш-коды адаптеров.

Читайте также:  Лучший лесной навигатор для андроид

Вывод: они используют один адаптер. Почему так?

Когда система создает список в виджете, она использует Intent, который мы передавали в метод setRemoteAdapter. В этом Intent мы указали, что надо использовать сервис MyService. Система биндится к MyService и передает ему этот Intent. Сервис проверяет, не был ли уже создан адаптер для такого Intent. Если был – то он и возвращается системе. Если по такому Intent еще не создавался адаптер, то он создается (используется метод onGetViewFactory, который мы реализовали) и возвращается системе. Т.е. некая система кэширования адаптеров по Intent.

Теперь наложим эту логику на нашу ситуацию. Мы создали первый виджет. В метод setRemoteAdapter передавали Intent с указанием класса нашего сервиса и с ID виджета в extra-данных. Сервис создал адаптер, отдал его списку первого виджета и связал эту пару – Intent и адаптер. Далее мы создаем второй виджет. Для его списка, мы использовали такой же Intent. Отличие только в extra-данных – ID виджета. Но сервис сверяет Intent-ы только по основным данным, без extra. Поэтому для него два этих Intent от разных виджетов получились одинаковы. И когда список второго виджета дал Intent и попросил выделить ему адаптер, сервис взял Intent, увидел, что по подобному Intent уже был выдан адаптер и его и использовал вместо того, чтобы новый городить. Т.е. список второго виджета получил тот же адаптер, что и список первого виджета.

Поэтому список второго виджета и показывает те же данные адаптера (ID виджета и хэш-код), что и список первого. Как это пофиксить? Сделать Intent-ы разными. Для этого будем добавлять к ним data, в который поместим все данные Intent. В этом случае у нас в data попадут extra-данные и Intent-ы будут разными.

Перепишем метод setList в MyProvider.java:

Теперь все ок. Для разных виджетов получатся разные Intent и списки получат разные адаптеры.

Для чистоты эксперимента удалите пару ранее созданных виджетов с экрана.

Все сохраняем, инсталлим. Размещаем пару виджетов

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

Осталось разобраться с реагированием на нажатия пунктов списка.

Добавим пару констант в класс MyProvider.java:

Заполним метод setListClick в MyProvider.java:

Здесь используется обычный алгоритм послания бродкаста. Мы с помощью метода setPendingIntentTemplate устанавливаем шаблонный PendingIntent, который затем будет использоваться всеми пунктами списка. В нем мы указываем, что необходимо будет вызвать наш класс провайдера (он же BroadcastReceiver) с action = ACTION_ON_CLICK.

Теперь нам надо сделать обработку этого action. Добавим метод onReceive в MyProvider.java:

Вызываем метод родителя, чтобы не нарушать работу провайдера. Далее проверяем, что action тот, что нам нужен — ACTION_ON_CLICK, вытаскиваем позицию нажатого пункта в списке и выводим сообщение на экран.

Осталось допилить адаптер. Перепишем getViewAt в MyFactory.java:

Для каждого пункта списка мы создаем Intent, помещаем в него позицию пункта и вызываем setOnClickFillInIntent. Этот метод получает на вход ID View и Intent. Что он с ними делает?

Для View с полученным на вход ID он создает обработчик нажатия, который будет дергать PendingIntent, который получается следующим образом. Берется шаблонный PendingIntent, который был привязан к списку методом setPendingIntentTemplate (в классе провайдера) и к нему добавляются данные полученного на вход Intent-а. Т.е. получится PendingIntent, Intent которого будет содержать action = ACTION_ON_CLICK (это мы сделали еще в провайдере) и данные по позиции пункта списка. При нажатии на пункт списка, этот Intent попадет в onReceive нашего MyProvider и будет обработан, как я уже чуть ранее описывал.

Все сохраняем, инсталлим. Проверяем – нажимаем на какой либо пункт:

Подытожим про LifeCycle-методы. Метод onCreate для адаптера вызывается, когда он создается для первого своего списка. А метод onDestory вызывается, когда удаляется последний список, использующий этот адаптер.

Мы использовали метод setRemoteAdapter. который на вход берет ID View и Intent, этот метод появился только в API 14. А изначально в API 11 была такая реализация — setRemoteAdapter (int appWidgetId, int viewId, Intent intent). Он на вход требовал еще ID виджета. Используйте этот вариант метода, если ваш виджет должен будет работать в Android 3.

У обычного ListView есть возможность установить View, которое будет отображаться если данных в списке нет — метод setEmptyView. RemoteViews также предоставляет вам такую возможность — setEmptyView. На вход передаете ID списка и ID пустого View.

На следующем уроке:

— рассмотрим прочие возможности виджета: превью, изменение размера, экран блокировки, ручное обновление

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Оцените статью