Создание виджета для андроид

Проектируем виджет

Цель нашего проекта — создать виджет, который периодически пингует указанные пользователем сайты и выводит соответствующую информацию на домашний экран. Традиционно будем использовать редактор кода Eclipse с плагином ADT.

В андроиде любой виджет представляет собой визуальный компонент, работающий в рамках того приложения, в которое он встраивается (как правило, это домашний экран). Кроме того, виджет умеет выводить устройство из режима ожидания, чтобы отобразить на экране актуальную информацию. Поэтому при разработке виджета нужно свести к минимуму время его обновления (можно, конечно, и пренебречь, но ANR в виджете — это уже моветон). Вполне логично напрашивается некоторый фоновый сервис, который будет пинговать сайты и записывать результат для дальнейшего анализа. Таким образом, задача виджета сведется к извлечению этих данных и выводу информации в виде текстовой строки — ссылки на сайт и некоторой графики — доступности сайта (рис. 1). Также нам потребуется простенькая форма для ввода списка подконтрольных сайтов, то есть GUI. Кстати, настоятельно рекомендую ознакомиться со статьей в мартовском номере «Хакера» «Хакерский Cron на Android», поскольку там подробно рассмотрена работа фонового сервиса.

Рис. 1. Хакерский виджет Другие статьи в выпуске:

Хакер #196. Все о Docker

  • Подписка на «Хакер»

Разрешения

Так как наша цель — сайты в интернете, необходимо получить соответствующие разрешения у пользователя в манифесте проекта (AndroidManifest.xml):

<uses-permission android:name=»android.permission.INTERNET»/> <uses-permission android:name=»android.permission.ACCESS_NETWORK_STATE»/>

Первая строка запрашивает разрешение на доступ в интернет, тогда как вторая позволяет отслеживать состояние подключения к сети (в терминологии Play Market — «просмотр состояния сети») — если сеть недоступна, пинговать что-либо особого смысла нет. Функция проверки подключения к сети выглядит следующим образом:

public boolean isConnected() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); if (ni != null && ni.isConnected()) { return true; } return false; }

Для доступа к сервису управления сетевыми подключениями используется константа Context.CONNECTIVITY_SERVICE, после чего метод getActiveNetworkInfo возвращает объект типа NetworkInfo, который содержит информацию о текущем соединении. Если подключение установлено, метод isConnected вернет true.

SQLite? No… Shared Preferences!

Как ты сам понимаешь, нам необходимо где-то хранить список сайтов и их (не)доступность. Сначала я хотел вновь использовать базу данных SQLite, но решил не повторяться и рассказать о другой полезной технологии Android — общих настройках (Shared Preferences). Общие настройки — довольно простой механизм, основанный на парах «ключ — значение» и предназначенный для сохранения примитивных данных приложения (число, строка, булево значение). С точки зрения Android настройки хранятся в виде обычного XML-файла внутри приватной директории приложения (data/data/имя_пакета/shared_pref/).

Для наших целей напишем небольшой класс (PingPref) для сохранения и чтения данных:

public class PingPref { private static final String PREF = «pref»; private static final String pre_ = «site»; private static final String _url = «_url»; private static final String _status = «_status»; private SharedPreferences mSettings; PingPref(Context context) { mSettings = context.getSharedPreferences(PREF, Context.MODE_PRIVATE); } public void setData(int num, String url, int status ){ Editor editor = mSettings.edit(); // Формируем строку вида siteN, где N — порядковый номер сайта String key = pre_ + String.valueOf(num); editor.putString(key + _url, url); // Ключ siteN_url editor.putInt(key + _status, status); // Ключ siteN_status editor.commit(); } public String getData(int num) { String key = pre_ + String.valueOf(num); String url = mSettings.getString(key + _url, «»); int status = mSettings.getInt(key + _status, -1); return new String {url, String.valueOf(status)}; } }

В конструкторе класса нужно вызвать метод getSharedPreferences в контексте приложения, указав имя файла общих настроек и режим доступа. Константа Context.MODE_PRIVATE указывает на то, что файл настроек будет доступен только внутри приложения (Google настоятельно рекомендует использовать только это значение). Каждый сайт будем хранить в двух ключах: siteN_url (ссылка) и siteN_status (доступность). В качестве последней используем число: 1 — сайт жив, 0 — сайт ушел (читай: «его ушли»), –1 — статус не определен (например, в случае отсутствия доступа к сети). Сеттеры (put.String, put.Int) и геттеры со значениями по умолчанию (get.String, get.Int) в пояснениях не нуждаются. Содержимое файла pref.xml в работе представлено на рис. 2.

Рис. 2. Pref.xml трудится

Фоновый сервис

Начиная с Android 3.0 (версия API 11), любой функционал, связанный с сетевой активностью, должен обязательно выполняться во вторичном потоке. Любая попытка подобной работы в главном (графическом) потоке приложения приведет к выбросу исключения NetworkOnMainThreadException и немедленному завершению программы. Это правило относится и к фоновому сервису, так как он тоже фактически выполняется в главном потоке. Как ты уже, наверное, догадался (а если нет — срочно покупай мартовский «Хакер»), мы будем использовать IntentService. Данный фоновый сервис берет на себя всю работу по созданию вторичного потока, позволяя нам сосредоточиться непосредственно на функционале.

Для начала зарегистрируем сервис в манифесте:

<service android:name=».PingService» > </service>

Основная работа сервиса кипит и бурлит внутри onHandleIntent, текст которого приведен ниже (отладочная печать присутствует):

private ArrayList<String> sites = new ArrayList<String>(4); private PingPref pf = new PingPref(this); private AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); … @Override protected void onHandleIntent(Intent intent) { Log.d(TAG_LOG, «Сеанс PingService работает…»); loadSites(); // Чтение списка сайтов boolean isConnected = isConnected(); // Есть соединение? if (!isConnected) { setSitesFail(); // Ставим для всех сайтов флаг -1 Log.d(TAG_LOG, «Соединение отсутствует!»); } else for (int i = 0; i < sites.size(); i++){ String site = sites.get(i); if (!site.equalsIgnoreCase(«»)) { // Сайт доступен? Да — flag=1, иначе flag=0 int flag = isSiteAvail(site)? 1 : 0; pf.setData(i+1, site, flag); Log.d(Main.TAG_LOG, site + «: flag=» + flag); } } refreshWidget(); // Обновляем виджет // Создаем отложенное намерение Intent si = new Intent(this, PingService.class); PendingIntent pi = PendingIntent.getService(this, 0, si, PendingIntent.FLAG_UPDATE_CURRENT); am.cancel(pi); // Сбрасываем предыдущую сигнализацию // Связь есть? Да — повтор пинга через 15 мин, иначе — проверка связи через 30 мин long updateFreq = isConnected ? AlarmManager.INTERVAL_FIFTEEN_MINUTES : AlarmManager.INTERVAL_HALF_HOUR; // Определяем время следующего запуска сервиса = текущее + интервал long timeToRefresh = SystemClock.elapsedRealtime() + updateFreq; // Устанавливаем сигнализацию am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, timeToRefresh, updateFreq, pi); Log.d(Main.TAG_LOG, «Следующий сеанс примерно через » + updateFreq/60/1000 + » мин.»); Log.d(Main.TAG_LOG, «Сеанс PingService завершен»); }

Функция loadSites заполняет коллекцию ArrayList адресами сайтов, сохраненных ранее в общих настройках. Далее происходит проверка на соединение с сетью: если isConnected возвращает false — для всех сайтов устанавливаем флаг -1 (setSitesFail), в противном случае запускаем цикл опроса всех ресурсов (isSiteAvail) с записью результатов (setData). Для активной работы со всемирной паутиной по протоколу HTTP в Java предусмотрен специальный класс HttpURLConnection, использующий объект-ссылку (URL) для указания адреса сайта:

private boolean isSiteAvail(String site){ try { URL url = new URL(site); HttpURLConnection urlc = (HttpURLConnection) url.openConnection(); urlc.setRequestProperty(«Connection», «close»); urlc.setConnectTimeout(5000); urlc.connect(); int Code = urlc.getResponseCode(); if (Code == HttpURLConnection.HTTP_OK) return true; } catch (MalformedURLException e) {} catch (IOException e) {} return false; }

Так как мы не собираемся в дальнейшем запрашивать каких-либо ресурсов с сайта, в заголовке запроса смело указываем лексему соединения setRequestProperty(«Connection», «close»), то есть после ответа сервер разорвет связь. Метод setConnectTimeout устанавливает тайм-аут соединения и подбирается экспериментально (в моем случае пять секунд при соединении 3G вполне хватило). Возвращаемое методом getResponseCode значение HttpURLConnection.HTTP_OK определяет положительный вердикт функции. Имей в виду: при использовании мобильного доступа к интернету шанс словить IOException и MalformedURLException весьма высок. Это происходит потому, что метод isConnected объекта NetworkInfo не всегда оперативно реагирует на изменение состояния сети, и мы можем прийти в isSiteAvail с отсутствующим соединением. Так что, если внезапно все сайты окажутся недоступными, паниковать, конечно, следует, но не сразу.

INFO

Объект HttpURLConnection обрабатывает только те ссылки, которые начинаются с http://, то есть протокол нужно указывать явным образом.

Функция refreshWidget инициирует обновление виджета посредством трансляции (передачи) уникального широковещательного намерения FORCE_WIDGET_UPDATE, которое наш виджет будет отлавливать, так как он является широковещательным приемником. Термин «широковещательность» означает глобальный характер обработки намерений — любое другое приложение может обработать наше намерение, равно как и мы можем подписаться на обработку чужого. Чтобы не было путаницы, намерения должны быть уникальными. Кстати, если например, нужно открыть интернет-ссылку (одно намерение), а в системе установлено несколько браузеров (несколько широковещательных приемников) — появится окно с выбором предпочитаемого. В следующем разделе мы рассмотрим этот механизм более подробно.

private void refreshWidget() { Intent i = new Intent(PingWidget.FORCE_WIDGET_UPDATE); sendBroadcast(i); }

Ближе к концу создаем уже знакомое тебе отложенное намерение на повторный запуск сервиса через не менее знакомый менеджер сигнализаций. Только вместо метода set будем использовать setRepeating, а точнее — setInexactRepeating. Последний помогает в некоторой степени уменьшить энергозатраты, собирая для выполнения близкие по времени сигнализации. Поэтому вместо точного интервала мы передаем константу AlarmManager.INTERVALFIFTEEN_MINUTES для опроса сайтов примерно через каждые 15 мин и AlarmManager.INTERVAL_HALF_HOUR (~30 мин) в случае отсутствия соединения для новой попытки. Возможно, ты захочешь указать другие константы объекта AlarmManager: INTERVAL_HOUR (час), INTERVAL_HALF_DAY (12 ч), INTERVAL_DAY (раз в сутки). Замечу, что эти интервалы очень_ условные, и при необходимости соблюдения более точного расписания следует использовать метод setRepeating, но, как уже отмечалось, он более прожорлив. К слову, будить устройство мы не станем — используем AlarmManager.ELAPSED_REALTIME, так как обновление информации для виджета при выключенном экране не только не требуется, но и, вероятно, вызовет укоризненный взгляд коллег из рубрики X-Mobile.

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

Виджет

В Android виджет реализуется в виде широковещательного приемника, реагирующего на некоторые события (строго говоря — намерения(Intent)), для наполнения визуальной разметки актуальными данными по таймеру, с помощью сервиса, по клику и так далее. Разработка виджета начинается с регистрации его класса (PingWidget) в манифесте проекта:

<receiver android:name=».PingWidget» android:label=»@string/app_name» > <intent-filter> <action android:name=»android.appwidget.action.APPWIDGET_UPDATE» /> </intent-filter> <intent-filter> <action android:name=»com.example.pinger.FORCE_WIDGET_UPDATE» /> </intent-filter> <meta-data android:name=»android.appwidget.provider» android:resource=»@xml/widget_provider» /> </receiver>

Здесь тег intent-filter содержит минимум одно стандартное действие — android.appwidget.action.APPWIDGET_UPDATE, используемое для обновления содержимого виджета (еще есть DELETED, ENABLED и DISABLED, но они необязательны). Так как обновлять виджет мы будем, во-первых, самостоятельно, во-вторых, в разные моменты времени, добавим еще одно действие — com.example.pinger.FORCE_WIDGET_UPDATE для нашей задачи. Кроме того, нам потребуется отдельный XML-файл, описывающий настройки виджета (файл res\xml\widget_provider.xml):

<appwidget-provider xmlns:android=»http://schemas.android.com/apk/res/android» android:initialLayout=»@layout/widget» android:minHeight=»110dp» android:minWidth=»250dp» android:resizeMode=»none» android:label=»@string/app_name» android:updatePeriodMillis=»86400000″ android:previewImage=»@drawable/widget_preview» />

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

min = 70 dp * (количество ячеек) – 30 dp.

Домашний экран в Android разделен виртуальной сеткой, состоящей из ячеек, размеры которых зависят от физических размеров устройства. Можно сказать, что ярлык приложения на домашнем экране соответствует одной ячейке. Наш виджет будет иметь размеры 4 х 2 или 250 dp x 110 dp (в аппаратно-независимых пикселях). Изменение размеров виджета пользователем мы не планируем, поэтому resizeMode устанавливаем в none.

Атрибут updatePeriodMillis задает минимальный период между обновлениями виджета (в миллисекундах) системой, но нам сейчас он неинтересен, так как виджет мы будем обновлять вручную, как только возникнет такая необходимость. Представь, наш фоновый сервис не запущен, а на рабочем экране висит виджет (типичное состояние устройства после перезагрузки) — Android вызовет процедуру его обновления незамедлительно, а уже потом через updatePeriodMillis миллисекунд. При первом обновлении просто запустим наш сервис, и как только он начнет работать, дальнейшее обновление информации в виджете будет инициировать именно он. Поэтому сейчас смело ставим 86 400 000 (то есть раз в сутки) и двигаемся дальше.

Если ты хочешь, чтобы в меню виджетов вместо иконки приложения красовалась симпатичная картинка (см. рис. 4), добавь ссылку на соответствующий ресурс в формате PNG в атрибуте previewImage. О том, как быстро и просто получить такое превью, читай врезку.

Рис. 4. Меню виджетов

Атрибут initialLayout позволяет указать разметку виджета в формате XML. Да, ты не ошибся, разметка виджета во многом напоминает разметку активности или диалогового окна — те же метки, кнопки, картинки, менеджеры компоновки и прочее. Фрагмент разметки нашего виджета представлен ниже (widget.xml):

<RelativeLayout xmlns:android=»http://schemas.android.com/apk/res/android» android:id=»@+id/widget» … android:alpha=»0.90″ android:background=»@drawable/widget» <ImageView android:id=»@+id/im1″ android:src=»@drawable/green» /> … /> <TextView android:id=»@+id/txt1″ android:text=»@string/app_name» …/>

Наш виджет состоит из небольших картинок (im1, im2, im3, im4) типа ImageView и текстовых меток (txt1, txt2, txt3, txt4) типа TextView. Для компоновки используем RelativeLayout, то есть все компоненты визуально выровнены друг относительно друга. Разумеется, картинки и надписи мы будем менять при отрисовке виджета. Поле alpha задает непрозрачность виджета в диапазоне от 0 (прозрачный) до 1 (непрозрачный), а вот background позволяет задать картинку для фона с эффектами стекла, бликами и тенями (да, я сторонник скевоморфизма). Для генерации такой текстуры можно воспользоваться онлайн-редакторами, которых в интернете очень много (см. полезные ссылки). Кстати, для правильного масштабирования виджета на разных экранах фон желательно перевести в формат NinePatch, о котором расскажет врезка.

Рис. 5. Виджет на смартфоне, а GitHub-то упал…

Итак, от визуальной стороны виджета (см. рис. 5) плавно переходим к логике его работы (класс PingWidget.java):

public class PingWidget extends AppWidgetProvider{ public static String FORCE_WIDGET_UPDATE = «com.example.pinger.FORCE_WIDGET_UPDATE»; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetIds) { startService(context); } @Override public void onReceive(Context context, Intent intent){ super.onReceive(context, intent); if (FORCE_WIDGET_UPDATE.equals(intent.getAction())) updateWidget(context); } private void updateWidget(Context context) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); ComponentName thisWidget = new ComponentName(context, PingWidget.class); int appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); drawWidget(context, appWidgetManager, appWidgetIds); } … }

NinePatch vs PNG

Изображения формата NinePatch (или растягивающиеся) — это файлы формата PNG, в которых области, предназначенные для масштабирования, помечены явным образом. Android SDK включает в себя визуальный редактор draw9patch, который находится по адресу SDK/Tools/draw9patch.bat.

На рис. 7 ты можешь видеть область растягивания нашего виджета (Patches), помеченную фиолетовым цветом. Какой бы размер виджета ни был, логотип твоего любимого журнала, а также закругленные уголки искажаться не будут. Эта область задается с помощью линеек слева и сверху от изображения. Обрати внимание на рамку толщиной в один пиксель с черными полосами — именно эта информация будет добавлена к исходному изображению.

Рис. 7. Размер — это главное!

На рис. 8 темным цветом показана область для контента (Content). Здесь мы видим, что все наши картинки и текстовые метки будут иметь небольшой отступ от рамки виджета. Эту красоту определяют линейки справа и снизу от изображения.

Рис. 8. Область для контента не уступает

Результат работы сохраняется в формате PNG c добавлением цифры 9 перед расширением файла (например, widget.9.png). При указании ссылки на графический ресурс указывать девятку не нужно (то есть android:background=»@drawable/widget»).

Класс AppWidgetProvider, являющийся широковещательным приемником, предоставляет нам удобные обработчики жизненного цикла виджета: onUpdate и onReceive (есть и другие — onDeleted, onDisabled, onEnabled, но мы их не рассматриваем). Первый вызывается при обновлении интерфейса виджета с периодичностью updatePeriodMillis (см. выше), как и договорились — просто запускаем сервис (как вариант, можно использовать уже полученные, но, возможно, неактуальные данные — все зависит от задачи). Второй, onReceive, срабатывает при получении определенного действия — FORCE_WIDGET_UPDATE, зарегистрированного нами ранее в манифесте проекта. Именно этот код и отвечает за манипуляции с картинками и текстовыми полями в UI виджета. Функция updateWidget сначала запрашивает объект класса AppWidgetManager, который применяется для обновления виджетов и предоставляет информацию о них. В частности, нас интересуют идентификаторы всех виджетов (массив appWidgetIds), ведь их может быть несколько и обновить нужно каждый из них:

private void drawWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetIds) { final int N = appWidgetIds.length; for (int i = 0; i < N; i++) { int appWidgetId = appWidgetIds; RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget); // Обновляем первую строку String data = pf.getData(1); views.setTextViewText(R.id.txt1, data); views.setImageViewResource(R.id.im1, getPicture(data)); … appWidgetManager.updateAppWidget(appWidgetId, views); } } private int getPicture(String type) { switch (type) { case «1»: return R.drawable.green; case «0»: return R.drawable.red; default: return R.drawable.gray; } }

В единственном цикле функции drawWidget происходит итерация по всем работающим в данный момент виджетам. Класс RemoteViews используется в качестве компонента для доступа к разметке, размещенной внутри процесса другого приложения (мне одному это напоминает Inject?). Если бы мы работали с активностью или фрагментом, то могли бы использовать вполне обычное findViewById. Но наш виджет работает в рамках домашнего экрана, поэтому для получения доступа к разметке необходимо в конструкторе RemoteViews указать название пакета (context.getPackageName()) и ресурс с разметкой (R.layout.widget). Используя views.setTextViewText и views.setImageViewResource, изменяем надпись и картинку (вспомогательная функция getPicture возвращает ссылку на подходящий ресурс). Как только мы внесли правки по всем строкам, фиксируем их в виджете, вызывая updateAppWidget.

Создание виджета для рабочего стола

AppWidget, или просто «виджет» — один из самых эффектных и удобных элементов пользовательского интерфейса в операционной системе Android, который можно добавить на рабочий стол для быстрого доступа к тем или иным функциям соответствующего приложения. В данной статье мы разберемся, как собственноручно создать свой виджет.

Что виджет представляет из себя конструктивно? Виджет нужен для отображения той или иной информации, связанной с приложением, к которому он относится, или же для оперативного взаимодействия с данным приложением напрямую через рабочий стол. Самый простой пример – часы на рабочем столе. Естественно, виджеты без труда можно добавлять или удалять с рабочих столов.

Качественно выполненные виджеты заметно оптимизируют взаимодействие с устройством. Более того, очень часто самые удобные виджеты занимают на рабочем столе минимум пространства, и при этом наиболее информативны. Практически любой пользователь будет стараться разместить на рабочих столах своего Android-гаджета максимальное количество информации, и важный момент заключается в том, что пространство ограничено.

Начнем создание виджета мы с дизайна. Благо, компания Google предоставила весьма полезные UI Guidelines для разработчиков программного обеспечения, где подробно описан процесс создания дизайна и основные принципы эргономичности. Есть также и отдельная официальная инструкция для создания виджетов, с которой можно ознакомиться по ссылке: http://developer.android.com/guide/practices/ui_guidelines/widget_design.html.

Виджет, занимающий одну «клетку» на рабочем столе, имеет разрешение 80х100 точек, соответственно, для создания продолговатый виджет длиной в 4 клетки и высотой в одну, то нужно, соответственно, 320х100 пикселей. Такой размер мы и возьмем за основу создаваемого нами виджета.

Теперь виджет нужно нарисовать. В принципе, виджет вполне может и не иметь никакой графической части, и отображать только текст или элементы управления, без фона и рамки, но, естественно, красивый и приятный глазу виджет просто обязан иметь качественный дизайн. Поэтому нарисуем фон. За основу возьмем фон из упомянутого выше UI Guideline. Открываем доступную нам заготовку в Photoshop или другом графическом редакторе и делаем всё, что заблагорассудится, после чего сохраняем полученное изображение в формате .png. Нужный нам формат PNG-24, с 8-битным цветом и прозрачным фоном. Вот и готова основа для нашего виджета.

Теперь перейдем к созданию программной части. Виджет может не иметь программной части. Проще говоря, в меню добавления виджетов он будет, но в основном меню приложений – нет. Мы создадим виджет именно такого типа. Создаем новый проект, и называем его для удобства так, чтобы основной класс имел имя widget.java.

Редактируем AndroidManifest.xml. Объявляем наш виджет:

<?xml version=»1.0″ encoding=»utf-8″?>

<manifest xmlns:android=»http://schemas.android.com/apk/res/android»

package=»com.example.widget»

android:versionCode=»1″

android:versionName=»1.0″>

<application android:icon=»@drawable/icon» android:label=»@string/app_name»>

<receiver android:name=»widget» >

<intent-filter>

<action android:name=»android.appwidget.action.APPWIDGET_UPDATE» />

</intent-filter>

<meta-data android:name=»android.appwidget.provider»

android:resource=»@xml/widget_info» />

</receiver>

</application>

</manifest>

Теперь редактируем widget.java. Тут необходимо описать, как будет реагировать виджет на различные условия. Класс AppWidgetProvider имеет такие методы:

onUpdate – метод вызывается при создании виджета, а также по истечении заданного времени. Время задается в конфигурационном файле данного виджета. Отметим, что используется чаще всего.

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

onEnabled – метод вызывается при первой активации виджета. Но если добавляется еще один точно такой же виджет, данный метод уже не выполняется.

onDisabled – метод выполняется тогда, когда удаляется последняя копия виджета с рабочего стола. Соответственно, данный метод является обратным onEnabled.

onReceive – метод вызывается одновременно со всеми остальными. Зачастую не используется вообще.

Сильно углубляться в программную часть виджета мы не будем, а потому не будем переполнять наш пример какими-либо обработчиками, а просто реализуем весь функционал посредством Layouts. Необходимо следующим образом объявить класс AppWidgetProvider:

package com.example.widget;

import android.appwidget.AppWidgetProvider;

public class widget extends AppWidgetProvider{

}

Далее, описываем наш виджет – это нужно для того, чтобы мобильный аппарат понимал, с чем имеет дело. Для этого нужно создать папку xml в папке res. В ней создаем файл с именем widget_info.xml. Открываем созданный файл и прописываем в него вот такой код:

<?xml version=»1.0″ encoding=»UTF-8″?>

<appwidget-provider xmlns:android=»http://schemas.android.com/apk/res/android»

android:minWidth=»294dp»

android:minHeight=»72dp»

android:updatePeriodMillis=»0″

android:initialLayout=»@layout/widget»>

</appwidget-provider>

Приведем краткое описание заданных параметров:

minWidth – минимальная необходимая для работы виджета ширина.

minHeight – минимальная необходимая для работы виджета высота.

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

initialLayout – параметр указывает на ресурс с описанием интерфейса нашего виджета.

Формула подсчета размеров виджета имеет такой вид: (количество клеток * 74) — 2.

Приступим к описанию интерфейса создаваемого нами виджета. Здесь-то нам и пригодится созданный ранее фон. Импортируем рисунок фона в папку dwawable (или во всех три папки drawable для разных разрешений экрана). В папке layout создаем файл с именем widget.xml. Интерфейс описывается как для обычных Activity, но есть некоторые ограничения. Допустимы для использования такие элементы:

LinearLayout

FrameLayout

RelativeLayout

AnalogClock

Button

Chronometer

ImageButton

ImageView

ProgressBar

TextView

Создадим LinearLayout, к которому применим созданную картинку-фон и добавим для примера AnalogClock. Сами часы в рамку не влезут, но как наглядный пример вполне сгодятся. Итак:

<?xml version=»1.0″ encoding=»utf-8″?>

<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»

android:id=»@+id/Widget»

android:layout_width=»fill_parent»

android:layout_height=»fill_parent»

android:orientation=»horizontal»

android:gravity=»center_vertical»

android:background=»@drawable/frame»>

<AnalogClock

Hello World widget для Android

Как ни странно, но на русском почти нет нормальных статей по виджетам для Android. Да и на англо язычных ресурсах почти нет простых примеров для старта, все примеры почему-то сложные и тяжелые для понимания. Спешу это исправить.
Структуру проекта и как его создать описывать не буду. Предполагается, что Вы это уже умеете, а для тех, кто не умеет советую почитать вот эту статью. Для виджета нам потребуется создать 3 файла:

  1. Widget provider info
  2. Widget provider
  3. Layout

Widget provider info – Это xml файл, описывающий метаданные виджета. К ним относятся размер виджета, частота его обновления, файл шаблона и класс конфигурации. Вот так будет выглядить наш файл (res/xml/hello_widget_provider.xml):
Размер виджета можем указывать любой, но гугл рекомендует придерживаться формуле расчёта размера виджета (number of cells * 74) – 2. updatePeriodMillis — это частота обновления виджета, но не чаще чем раз в 30 минут, в целях экономии батарейки. initialLayout – это файл шаблона виджета.
Widget provider — Это java файл, он должен наследоваться от класса AppWidgetProvider. В нашем случае он пока останется пустым (src/ru/example/android/widget/ HelloWidget.java).
Layout – Это шаблон виджета или слой View, кому как нравится. Выглядеть он будет так: (res/layout /main.xml).
Всё основное мы сделали, осталось зарегистрировать виджет в AndroidManifest.xml. Для этого добавим в него следующий код в раздел <application>…</application>:
Теперь можем компилировать проект и смотреть результат в эмуляторе!

Наш виджет хоть и работает, но абсолютно бесполезен. Давайте сделаем так, чтобы он реагировал на нажатие кнопки.
В виджете невозможно повесить полноценное событие на нажатие кнопки или еще на какое-либо событие, как это Вы привыкли делать в Activity. На этом примере Вы увидите, как можно обработать событие от нажатия кнопки. Давайте для начала добавим в наш шаблон кнопку (res/layout /main.xml).

Все взаимодействия с виджетом будем делать в классе provider (src/ru/example/android/widget/ HelloWidget.java). Вот как будет выглядеть простейшая обработка события:
В классе есть 2 метода — onUpdate и onReceive. Метод onUpdate вызывается при обновлении виджета. Частоту обновления мы настроили в файле res/xml/hello_widget_provider.xml атрибутом android updatePeriodMillis=»86400000″. Метод onReceive унаследован от класса BroadcastReceiver.
В виджете нельзя обновить отдельный элемент, например текст, как в Activity. Всегда обновляется иерархия Views целиком. Для обновления виджета нам потребуется класс RemoteViews, с помощью которого мы и будем менять иерархию Views целиком. К сожалению, возможности этого класса скудные. Он позволяет нам изменять текст, картинки и вешать событие на клик. Событие в виджете событием можно назвать с натяжкой, api позволяет выполнять всего 3 дейстия:

  • Бросить Broadcast
  • Запустить Activity
  • Запустить Service

В нашем случае мы будем рассылать Broadcast (Широковещательное сообщение). В результате получится что-то вроде обычной обработки события. С помощью класса PendingIntent создаём наше событие и регистрируем его в RemoteViews. Затем обновляем виджет. А в методе onReceive ловим наше «событие» и обрабатываем, выводя сообщение с помощью класса Toast.
Добовляем изменения в файл AndroidManifest.xml:
Компилируем, и наслаждаемся резульатом.

Ссылки по теме:

  • Пишем своё первое приложение на Android
  • Подсвечиваемый виджет в Android
  • Пишем виджет ХабраКарма ex-CarmaWidget для Android

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

Сегодня мы разберемся в том, как создать на Android устройство свой собственный виджет (Widget). Виджет — это всем знакомый элемент рабочего стола, с помощью которого можно получать доступ к некоторым функциям какого — нибудь приложения: просматривать новости в окне виджета, прогноз погоды, обновление новостей на разных сервисах, управлять разными функциями аппарата (блокировать экран, включать радио, Интернет и многое многое другое). На этот раз мы не будем создавать чего то грандиозного и очень полезного, типа там фонарика :), а сделаем простенький виджет, который будет реализован в виде кнопки, при нажатии на которую мы, с помощью стандартного браузера, попадаем на всеми любимый сайт http://learn-android.ru. Конечно, вы сможете настроить любой желаемый вами сайт.

Создаем новые проект, выбираем Blank Activity, минимальная версия Android 2.2+. При создании виджета, первое дело — создать объект AppWidgetProviderInfo, в котором мы укажем xml файл, из которого будет заполняться вид самого виджета. Для этого, создадим в проекте папку res/xml и в ней создаем новый xml файл по имени widget.xml со следующим содержимым:

<?xml version=»1.0″ encoding=»utf-8″?> <appwidget-provider xmlns:android=»http://schemas.android.com/apk/res/android» android:minWidth=»146dp» android:updatePeriodMillis=»0″ android:minHeight=»146dp» android:initialLayout=»@layout/activity_main»> </appwidget-provider>

Теперь перейдем в файл activity_main.xml и создадим интерфейс нашего виджета, он будет состоять из кнопки Button:

<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android» xmlns:tools=»http://schemas.android.com/tools» android:layout_width=»match_parent» android:layout_height=»match_parent» android:gravity=»top» tools:context=».MainActivity» > <Button android:id=»@+id/button» android:layout_width=»wrap_content» android:layout_height=»wrap_content» android:layout_marginLeft=»5dp» android:text=»@string/app_name» /> </LinearLayout>

Как видите, мы создали обычную кнопочку, вот она и будет нашим виджетом:

То есть, можете потом сделать вместо этой кнопочки все, что вам угодно.

Перейдем к работе с кодом в файле MainActivity.java. Он должен наследоваться от класса AppWidgetProvider, для которого существует его основной метод onUpdate (). В этом методе нам нужно обязательно определить два объекта: PendingIntent и RemoteViews. В конце их использования нужно вызвать метод updateAppWidget(). Код файла MainActivity.java:

import android.net.Uri; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.Toast; import com.example.widget.R; public class MainActivity extends AppWidgetProvider{ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetIds) { for(int i=0; i<appWidgetIds.length; i++){ int currentWidgetId = appWidgetIds; //Делаем простой http запрос на указанную ссылку и выполняем по ней переход: String url = «http://learn-android.ru»; Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse(url)); //Определяем два обязательных объекта класса PendingIntent и RemoteViews: PendingIntent pending = PendingIntent.getActivity(context, 0,intent, 0); RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.activity_main); //Настраиваем обработку клика по добавлению виджета: views.setOnClickPendingIntent(R.id.button, pending); appWidgetManager.updateAppWidget(currentWidgetId,views); Toast.makeText(context, «Виджет добавлен», Toast.LENGTH_SHORT).show(); } } }

Чтобы наш виджет успешно заработал, нужно немного магии в файле манифеста AnroidManifest.xml. Он должен выглядеть вот так:

<?xml version=»1.0″ encoding=»utf-8″?> <manifest xmlns:android=»http://schemas.android.com/apk/res/android» package=»com.example.widget» android:versionCode=»1″ android:versionName=»1.0″ > <uses-permission android:name=»android.permission.INTERNET»/> <application android:allowBackup=»true» android:icon=»@drawable/ic_launcher» android:label=»@string/app_name» android:theme=»@style/AppTheme» > <receiver android:name=»home.study.MainActivity» > <intent-filter> <action android:name=»android.appwidget.action.APPWIDGET_UPDATE» /> </intent-filter> <meta-data android:name=»android.appwidget.provider» android:resource=»@xml/widget» /> </receiver> </application> </manifest>

Как вы догадались, виджет определяется в теге <receiver> </receiver>.

Единственное, что осталось подправить — отредактировать файл strings.xml, добавив туда используемые нами строчки:

<?xml version=»1.0″ encoding=»utf-8″?> <resources> <string name=»app_name»>LEARN.ANDROID</string> <string name=»action_settings»>Settings</string> <string name=»hello_world»>Hello world!</string> </resources>

Внимание! Android Studio может заругаться на вас при запуске программы, требую указать default activity. Выберите строчку «Не запускать activity» (Do not launch Activity):

Теперь устанавливаем приложение на эмулятор либо устройство, добавляем виджет на рабочий стол:

И жмем по нему:

Как видите, все отлично работает.

Итак, в этом уроке мы создали простенький Android Widget, состоящий из кнопки Button, при нажатии на наш виджет мы создаем простой http запрос и переход по заданной URL ссылке. Можете поупражняться с какими-нибудь другими элементами, а не кнопкой, и придумать им интересный функционал. Удачи!

Виджеты представляют собой миниатюрные приложения, которые располагаются на домашнем экране устройства под управлением операционной системой Android.

Виджеты могут показывать время, погоду, курсы валют и другую полезную информацию. Также виджеты могут выполнять какие-либо действия.

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

Создание виджета можно условно разделить на две части.

  1. Описание виджета;
  2. Реализация программной логики.
1.Описание виджета

Описание виджета содержит информацию о его параметрах, внешнем виде.

Описание состоит из трёх частей

  1. Объявление виджета в манифесте;
  2. Создание xml файла с основными параметрами;
  3. Создание xml файла разметки внешнего вида (интерфейса пользователя).

Объявление виджета в манифесте представляет собой секцию receiver, которая включает ключевую информацию о виджете.

А, именно (сверху вниз):

  1. Имя виджета;
  2. Интент фильтр для события, на которое будет реагировать виджет;
  3. Секция метаданных. Содержит информацию о том, что компонент, описываемый данной секцией receiver, является виджетом, а также имя xml файла с его параметрами.

Файл параметров виджета находится в папке xml и носит название имявиджета_info. Он состоит из единственной секции appwidget-provider. Параметры указываются в качестве атрибутов этого тега.

Например:

Эти параметры означают (сверху вниз):

  1. Файл разметки внешнего вида (интерфейса пользователя) для экрана блокировки (keyguard);
  2. Файл разметки внешнего вида (интерфейса пользователя) для использования в обычном режиме;
  3. Минимальная высота виджета;
  4. Минимальная ширина виджета;
  5. Изображение значка виджета для меню приложений;
  6. Возможность изменения размера виджета по горизонтали или вертикали. В данном примере включена возможность изменения размера виджета в обоих направлениях;
  7. Период обновления данных виджета в миллисекундах. Может быть указано любой число, но обновляться виджет будет не чаще 1 раза в 30 минут.

Файл разметки внешнего вида расположен в папке layout и в принципе ничем не отличается от аналогичных файлов, например, для Activity. Разве, что значения свойств ответственных за внешний вид по умолчанию адаптированы для виджета.

Пример:

2.Реализация программной логики работы виджета

Виджет является наследником класса AppWidgetProvider, который в свою очередь является наследником класса BroadcastReceiver, добавляя необходимый функционал.

В классе AppWidgetProvider предусмотрена группа из пяти методов, так называемого, «жизненного цикла» виджета, которые нужно переопределить в классе, описывающем конкретный виджет.

  • onEnabled
    Вызывается только один раз, когда экземпляр виджета создаётся впервые. То есть, когда пользователь добавляет виджет на домашний экран;
  • onDeleted
    Вызывается каждый раз когда экземпляр виджета удаляется из коллекции виджетов;
  • onDisabled
    Вызывается при удалении последнего экземпляра виджета;
  • onUpdate
    Вызывается когда необходимо обновить виджет согласно временному интервалу указанному в параметре updatePeriodMillis или по команде из программы (есть альтернативный подход к обновлению);
  • onReceive
    Вызывается перед каждым из вышеперечисленных методов и при получении Broadcast Intent. Используется, в основном, для реализации обработки событий.
    Единственный из методов «жизненного цикла», который не требуется обязательно переопределять.

Главную роль в работе виджета играют два метода: onUpdate и onReceive. Именно они обеспечивают большую часть его функционала.

Как уже отмечено выше, метод onUpdate выполняет обновление виджета – отображение актуальной версии тех или иных данных виджетом, расположенным на домашнем экране устройства.

Обновление виджета достаточно сложный процесс. Но, так как он уже был подробно описан в предыдущих статьях, не станем заострять на нём внимание.

Статьи, посвящённые обновлению виджетов:

  • Обновление виджета для домашнего экрана Android по таймеру;
  • Обновление виджета для домашнего экрана Android по запросу из приложения.

Для того чтобы обработать в коде виджета те или иные события, нужно задействовать метод onReceive и отфильтровать в нём соответствующий Intent.

Дело в том, что метод onReceive реагирует практически на все broadcast intent’ы, на которые способен реагировать данный конкретный виджет. Поэтому необходимо проверить, какой именно Intent поступил и только после этого выполнять действия.

Java

К сожалению, только таким образом можно обеспечить селективность обработки Intent.

Приведённый механизм используется также для обработки событий пользовательского интерфейса. Для этого на соответствующие элементы виджета устанавливается PendingIntent, который обрабатывается в onReceive.

Ниже представлен пример реализации обработки события клика по текстовой надписи на виджете.

Java

Здесь важно отметить, ещё одну характерную особенность виджетов. Их внешний вид, описанный в файле разметки, доступен только при помощи объекта RemoteViews. Это обусловлено тем, что визуальное представление виджетов работает в другом процессе – процессе домашнего экрана. Вследствие этого обращение к нему напрямую, как в Activity, невозможно.

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

Всё-таки задача виджетов лишь дополнить основное приложение и сделать его использование более удобным.

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

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