Полный список
— создаем простой виджет
— разбираемся в его 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, разумеется, не всплывет. В консоли должен появиться текст:
\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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Code a Widget for Your Android App: Updating the Widget
Application widgets provide your users with easy access to your application’s most frequently used features, while giving your app a presence on the user’s homescreen. By adding a widget to your project, you can provide a better user experience, while encouraging users to remain engaged with your application, as every single time they glance at their homescreen they’ll see your widget, displaying some of your app’s most useful and interesting content.
In this three part series, we’re building an application widget that has all the functionality you’ll find in pretty much every Android application widget.
In the first post, we created a widget that retrieves and displays data, and performs a unique action in response to onClick events. However, our widget is still missing one crucial piece of functionality: it never updates with new information. A widget that never has anything new to offer isn’t all that useful, so we’ll give our widget two different ways to update.
By the end of this series, we’ll have expanded our widget to retrieve and display new data automatically based on a schedule, and in response to user interaction.
We’re picking up right where we left off, so if you don’t have a copy of the widget we created in the first post, then you can download it from GitHub.
Updating Your Layout
The first step is updating our layout to display some data that changes over time. To help us see exactly when our widget receives each update, I’m going to instruct the widget to retrieve and display the current time, whenever it performs an update.
I’m going to add the following to the widget layout:
- A TextView that displays a Last Update label.
- A TextView that will display the time of the last update.
While we’re working on the new_app_widget.xml file, I’m also going to add the TextView that’ll eventually allow the user to trigger an update manually:
Next, define the new string resources that we reference in our layout:
The @string/time looks different from your typical string, as it’s just a placeholder that’ll be replaced at runtime.
This gives us the finished layout:
Updating Your Widget Based on a Schedule
Since it’s the easiest method to implement, I’m going to start by updating our widget automatically, after a set period of time has elapsed.
When creating this kind of automatic update schedule, 1800000 milliseconds (30 minutes) is the smallest interval you can use. Even if you set your project’s updatePeriodMillis to less than 30 minutes, you widget will still only update once every half an hour.
Deciding how frequently your widget should update is never straightforward. Frequent updates are a greater drain on the device’s battery, but set these updates too far apart and your widget may wind up displaying significantly out of date information to the user.
To find a balance that works for your particular project, you’ll need to test your widget across a range of update frequencies and measure the impact each frequency has on battery life, while monitoring whether the widget’s content ever becomes noticeably out of date.
Since how-frequent-is-too-frequent is one of those frustrating subjective questions where there’s no “right” answer, it can help to get a second opinion by arranging some user testing. You could even setup some A/B testing to check whether certain update frequencies are received more positively than others.
Open your project’s AppWidgetProviderInfo file (res/xml/new_app_widget_info.xml) and you’ll see that it already defines a default update interval of 86400000 milliseconds (24 hours).
There are plenty of widgets that only update once every 24 hours, for example a widget that displays the weather forecast might only need to retrieve new information once per day. However, even if your widget does only need to update once per day, this isn’t ideal when testing your app. No-one wants to wait 24 hours just to see whether their onUpdate() method is triggering correctly, so you’ll typically use the shortest possible interval when performing initial testing, and then change this value later, if required.
To make it easier to test our app, I’m going to use the smallest possible interval:
Retrieving and Displaying the Current Time
The onUpdate() method is responsible for updating your widget’s Views with new information. If you open your project’s widget provider (java/values/NewAppWidget.java) then you’ll see that it already contains the outline of an onUpdate() method:
Regardless of whether your widget updates automatically or in response to user interaction, the update process is exactly the same: the widget manager sends a broadcast intent with the ACTION_APPWIDGET_UPDATE action, and the widget-provider class responds by calling the onUpdate() method, which in turn calls the updateAppWidget() helper method.
Once we’ve updated our NewAppWidget.java class to retrieve and display the current time, our project will use this same section of code regardless of how the onUpdate() method is triggered:
There’s one problem with the above code: if the user taps the TextView multiple times during the span of a minute, there’ll be no visual indication that the widget has updated, simply because there’s no new time to display. This probably won’t cause problems for your end-users, who are unlikely to sit there, tapping the TextView multiple times per minute and wondering why the time hasn’t changed. However, this may be a problem when testing your app, as it means you’ll have to wait at least 60 seconds in between triggering the onUpdate() method.
To ensure there’s always some kind of visual confirmation that the onUpdate() method has ran successfully, I’m going to display a toast whenever onUpdate() is called:
Updating Your Widget in Response to User Action
Our widget will now update automatically once every half an hour, but what about updating in response to user interaction?
We’ve already laid a lot of the groundwork by adding a Tap to Update TextView to our layout and expanding the NewAppWidget.java class to retrieve and display the current time whenever onUpdate() is called.
The only thing left to do is create an Intent with AppWidgetManager.ACTION_APPWIDGET_UPDATE , which is an action that informs the widget that it’s time to update, and then call this Intent in response to the user interacting with the Tap to Update TextView .
Here’s the completed NewAppWidget.java class:
Being able to update a widget on demand can be invaluable when testing your project, as it means you’ll never have to wait for the update interval to elapse. Even if you don’t include this functionality in your finished application, you may want to add a temporary Tap to Update button, just to make your life easier when testing your project.
Creating a Preview Image
Our widget now looks completely different to the widget that Android Studio generated for us automatically, but at the moment it’s still using the auto-generated preview image.
Ideally, your preview image should encourage users to select your widget from the Widget Picker, but at the very least it should be an accurate representation of how your widget actually looks! Currently, our preview image ticks neither of those boxes, so we need to create a new one.
The easiest method, is to use the Widget Preview application that’s included in the Android emulator.
Creating a Widget Preview
First, install your project on an Android Virtual Device (AVD). Then open the AVD’s app drawer and launch the Widget Preview application. The AVD will display a list of every app that’s installed on the device—select your app from the list.
Your widget will be displayed on a blank background. Spend some time resizing and tweaking your widget so that it looks its best, and once you’re happy with the results, select Take Snapshot. The screenshot will be saved as a PNG in the AVD’s Download folder. To retrieve this file, switch back to Android Studio and open the Device File Explorer, by selecting View > Tool Windows > Device File Explorer from the toolbar. In the Device File Explorer view, navigate to the sdcard/Download folder, where you’ll find the preview image saved as: [app_name]_ori_[orientation].png
Drag this image out of Android Studio and drop it somewhere easily accessible, such as your Desktop. Give the image a more descriptive name, then drag and drop the image into your project’s drawable folder. Now you can open the AppWidgetProviderInfo file (in this instance, that’s new_app_widget_info.xml) and change the following line to reference your new preview image:
Finally, you can remove the unnecessary example_appwidget_preview drawable from your project.
Testing Your Widget
It’s time to put the finished widget to the test!
First, install the updated project on your physical Android device or AVD. Remove all existing instances of this widget from your homescreen, so you know you’re working with the latest version.
To add your widget, press any empty space on the homescreen, tap Widget and then select your widget. You’ll get a chance to resize and reposition the widget, as necessary.
The Last Update value will display the time that you created this widget. Press Tap to Update and the widget should display a toast and a new time. If you place a second instance of the widget on your homescreen it should display a different time to the first one.
You can update either of these widget instances, by giving its Tap to Update TextView a tap. If you trigger a manual update for the first widget, then the second widget will not be updated, and vice versa.
In addition to manual updates, the App Widget Manager will update all instances of your widget once every 30 minutes, based on the time when you created the first instance. Although manual updates can result in instances displaying different times, once every half an hour all instances will be updated simultaneously, at which point they’ll display the same time.
You may want to keep a few instances of this widget on your homescreen, so you can monitor how their content changes over time in response to a combination of automatic and manual updates.
Conclusion
In this series, we’ve been creating an Android application widget that demonstrates how to implement all the most common features found in app widgets. If you’ve been following along since the beginning, then by this point you’ll have built a widget that updates automatically and in response to user input, and that’s capable of reacting to onClick events (you can also download the complete project from GitHub).
In the next post, we’ll look at some best practices for ensuring your widget provides a good user experience, and how to enhance your widget with a configuration Activity.
Источник