Урок 122. Виджеты. Превью, изменение размера, экран блокировки, ручное обновление
В этом уроке:
- рассмотрим прочие возможности виджета: превью, изменение размера, экран блокировки, ручное обновление
В прошлых уроках мы рассмотрели все основные моменты, касающиеся виджетов. Осталось поговорить о нескольких небольших, но полезных возможностях.
Не буду создавать отдельный проект под это, покажу только фрагменты кода и скрины. Знаний, полученных в предыдущих уроках, должно хватит, чтобы понять, о чем идет речь.
Превью
(API Level 11)
Когда вы хотите добавить виджет, вы открываете экран выбора и видите там список виджетов с названиями и картинками. По умолчанию, вместо картинки идет иконка приложения. А можно поставить туда свое изображение.
Я сделаю простой виджет, показывающий время при обновлении
Мой виджет называется WidgetFeatures и в списке виджетов он выглядит так:
В качестве превью-изображения сейчас используется иконка приложения и это не очень информативно.
Поставим свою картинку. Я сделаю скрин Home-экрана с моим виджетом, затем обрежу картинку до такого состояния
закину в папку res/drawable и пропишу ее в файле метаданных виджета
android:previewImage="@drawable/screen"
Теперь виджет в списке будет выглядеть так:
И пользователь имеет примерное представление, как будет выглядеть виджет после установки.
Изменение размера
(API Level 12)
Вы можете предоставить пользователю возможность менять размеры виджета.
Для этого необходимо прописать в метаданных:
android:resizeMode="horizontal|vertical"
В этом случае виджет будет способен менять размер и по вертикали и по горизонтали. Если вам нужно только одно направление, оставьте только его в значении атрибута.
Вы можете растянуть виджет на сколько это позволит свободное место на экране. Но не сможете сделать его меньше, чем изначально указанные размеры minHeight и minWidth.
Для этого в API Level 14 появляются еще два атрибута для метаданных: minResizeWidth и minResizeHeight. Они как раз и ставят минимальные значения для изменения размера виджета, а атрибуты minHeight и minWidth становятся значениями по умолчанию и используются для задания первоначального размера виджета при добавлении.
В API Level 16 в классе провайдера появляется метод onAppWidgetOptionsChanged. Метод срабатывает, когда вы меняете размер виджета. Он предоставляет вам Bundle с данными (тип - int, единицы измерения - dp) о новом размере. Данные можно извлечь с помощью этих ключей:
OPTION_APPWIDGET_MIN_WIDTH
OPTION_APPWIDGET_MIN_HEIGHT
OPTION_APPWIDGET_MAX_WIDTH
OPTION_APPWIDGET_MAX_HEIGHT
Как видно по названию переменных, система возвращает не точное значение новой ширины или высоты виджета, а диапазон с минимальным и максимальным значением. Не знаю каким образом формируются эти значения, но нам предлагается использовать их, чтобы подстраивать содержимое виджета под новые размеры.
Для примера выведу эти цифры в виджет.
Вот виджет шириной в две ячейки и высотой в одну (2х1).
А вот, что будет, если его растянуть до 3х2
Растянем до 4х3
Данные о текущем размере виджета также можно получить с помощью метода getAppWidgetOptions класса AppWidgetManager. Он вернет вам Bundle с этими же параметрами.
Экран блокировки
(API Level 17)
Атрибут widgetCategory может определять возможность размещения виджета не только в Home (home_screen), но и на экране блокировки (keyguard) .
android:widgetCategory="home_screen|keyguard">
В этом случае виджет появится в списке виджетов для экрана блокировки
И после добавления он будет виден
Чтобы программно определить где расположен виджет, используйте снова метод getAppWidgetOptions. Он вернет вам Bundle, из которого по ключу OPTION_APPWIDGET_HOST_CATEGORY можно вытащить категорию (тип int). Она может быть равна либо WIDGET_CATEGORY_HOME_SCREEN (home), либо WIDGET_CATEGORY_KEYGUARD (экран блокировки).
В метаданных вы также можете отдельно задать layout, использующийся для виджета на экране блокировки. Это атрибут android:initialKeyguardLayout. Аналогично атрибуту android:initialLayout указываете там layout-файл. Правда, у меня он почему-то не сработал и виджет с экрана блокировки использовал тот же layout, что и виджет на экране Home. Я не понял, почему.
Ручное обновление виджетов
Обычно система сама посылает broadcast-сообщения о том, что пора обновлять виджет, и минимальный порог, который она позволяет задать – это полчаса. Мы можем обойти это и назначить свое расписание. Есть несколько вариантов, как это организовать и что откуда вызывать. Я покажу один из них.
Все это делается в классе провайдера.
Создаем переменную:
final String UPDATE_ALL_WIDGETS = "update_all_widgets";
Далее пишем в методах onEnabled и onDisabled:
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
Intent intent = new Intent(context, MyWidget.class);
intent.setAction(UPDATE_ALL_WIDGETS);
PendingIntent pIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(),
60000, pIntent);
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
Intent intent = new Intent(context, MyWidget.class);
intent.setAction(UPDATE_ALL_WIDGETS);
PendingIntent pIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pIntent);
}
Содержимое двух этих методов почти одинаково, только первый запускает посылку broadcast сообщения каждую минуту с помощью AlarmManager, а второй отменяет это.
В методе onReceive пишем:
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equalsIgnoreCase(UPDATE_ALL_WIDGETS)) {
ComponentName thisAppWidget = new ComponentName(
context.getPackageName(), getClass().getName());
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
int ids[] = appWidgetManager.getAppWidgetIds(thisAppWidget);
for (int appWidgetID : ids) {
updateWidget(context, appWidgetManager, appWidgetID);
}
}
}
где updateWidget – это ваш метод, в котором прописана логика обновления виджета.
Здесь мы ловим broadcast, который каждую минуту шлет AlarmManager. Далее создаем ComponentName, соответствующий провайдеру виджета нашего приложения и, используя его, получаем список ID виджетов с помощью метода getAppWidgetIds. Далее этот список прогоняем через метод обновления.
И не забудьте поставить в метаданных атрибут updatePeriodMillis = 0, чтобы выключить для виджета рассылку broadcast-сообщений от системы.
Тем самым, мы задали свое расписание обновления. Оно включится при создании первого экземпляра виджета (или при старте системы, если есть экземпляры виджета) и выключится при удалении последнего экземпляра.