На прошлом уроке вы научились создавать свой собственный сайдбар , а на этом уроке вы узнаете как создать свой виджет на WordPress . Виджет - это модуль, который предназначен как раз для размещения на сайдбаре.

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

На WordPress уже есть стандартные виджеты, которые можно увидеть в левой части админ-консоли. На вкладке - Внешний вид / Виджеты , правую часть занимают сайдбары, куда можно мышкой перетащить все доступные виджеты.

Как создать свой виджет на WordPress

Откроем кодекс WordPress и посмотрим, что есть в документации насчёт создания своего виджета . WordPress предлагает расширить стандартный класс WP_Widget и его функции и демонстрирует пример готового кода с одним текстовым полем. New_Widget расширяет встроенный в WordPress виджет WP_Widget .

Class New_Widget extends WP_Widget {

Код для создания нового виджета с дефолтным названием Foo_Widget , вам надо это название везде заменить на своё - New_Widget .

Надо добавить новый класс New_Widget , скопируйте код из Example в кодексе и вставьте в файл function.php . Необходимо ещё зарегистрировать новый виджет, делается это с помощью хука widgets_init , хук цепляет добавленный виджет к ядру WordPress . Ниже пример регистрации виджета New_Widget , добавьте этот код в файл function.php , сразу после кода с классом.

Function register_new_widget() {
register_widget("New_Widget");
}
add_action("widgets_init", "register_new_widget");

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

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

Файл function.php не резиновый

Если у вас слишком много пользовательского кода, то я рекомендую код каждого нового класс виджета выносить в отдельный PHP файл, которые складывать в свою отдельную папку с виджетами. Кроме того для кода регистрации всех кастомных виджетов необходимо создать ещё один файл. Теперь надо подключить эти файлы к function.php через функцию require .

Require get_template_directory() . "/widgets/custom-widget-1.php";
require get_template_directory() . "/widgets/custom-widget-2.php";

Как отключить виджеты

В случае, если вы не собираетесь использовать какие-то стандартные или собственные виджеты, то можно их отключить. Для этого у WordPress предусмотрена функция - unregister_widget() , она не удаляет виджеты из WordPress насовсем, а убирает их просто их админ-консоли. Вставьте в файл function.php названия виджетов, которые вам надо отключить.

Function remove_calendar_widget() {
unregister_widget("WP_Widget_Calendar");
}

А так же не забудьте убрать хук, связывающий с ядром WordPress .

Add_action("widgets_init", "remove_calendar_widget");

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

Contact Form 7 - плагин формы обратной связи

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

На сайте в сайдбаре появится виджет с готовой формой обратной связи.

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

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

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

Начнем создание виджета мы с дизайна. Благо, компания Google предоставила весьма полезные UI Guidelines для разработчиков программного обеспечения, где подробно описан процесс создания дизайна и основные принципы эргономичности. Есть также и отдельная официальная инструкция для создания виджетов, с которой можно ознакомиться по ссылке: //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 . Объявляем наш виджет:

package=»com.example.widget»

android:versionCode=»1″

android:versionName=»1.0″>

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

Теперь редактируем 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 . Открываем созданный файл и прописываем в него вот такой код:

android:minWidth=»294dp»

android:minHeight=»72dp»

android:updatePeriodMillis=»0″

android:initialLayout=»@layout/widget»>

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

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

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

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

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

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

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

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

android:id=»@+id/Widget»

android:layout_height=»fill_parent»

android:orientation=»horizontal»

android:gravity=»center_vertical»

android:background=»@drawable/frame»>

android:layout_width=»fill_parent»

android:layout_height=»wrap_content»/>

Создавать виджет WordPress – это примерно, как создавать плагин, но гораздо проще и понятнее. Все, что вам нужно, это один файл со всем PHP кодом, который писать гораздо проще, чем плагин, у которого несколько файлов. Есть три основные функции виджета: это widget , update и form .

  • function widget()
  • function update()
  • function form()

| Скачать исходники |

Базовая структура

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

Add_action("widgets_init", "register_my_widget"); // function to load my widget function register_my_widget() {} // function to register my widget class My_Widget extends WP_Widget () {} // The example widget class function My_Widget() {} // Widget Settings function widget() {} // display the widget function update() {} // update the widget function form() {} // and of course the form for the widget options

Шаг 1. widget_init

Перед тем, как мы все это сделаем, нам нужно загрузить наш виджет с помощью функции widget_init . Это зацепка к action , о которой вы можете найти больше информации в WordPress codex .

Add_action("widgets_init", "register_my_widget");

Следующее, что мы сделаем, это зарегистрируем наш виджет в WordPress, чтобы он был доступен в разделе виджетов.

Function register_my_widget() { register_widget("My_Widget"); }

Шаг 2. Класс

Мы заключим наш виджет в класс . Имя класса имеет значение! Что мы должны иметь в виду, так это что имя класса и имя функции должны быть одинаковыми .

Class My_Widget extends WP_Widget {}

Теперь мы передадим классу некоторые параметры настроек. Например, мы можем передать ширину и высоту . Мы также можем дать небольшое описание нашему виджету, если хотим. Это будет полезно, если вы связываете виджет с вашей коммерческой темой.

Function My_Widget() { function My_Widget() { $widget_ops = array("classname" => "example", "description" => __("A widget that displays the authors name ", "example")); $control_ops = array("width" => 300, "height" => 350, "id_base" => "example-widget"); $this->WP_Widget("example-widget", __("Example Widget", "example"), $widget_ops, $control_ops); }

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

Шаг 3. function widget()

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

Function widget($args, $instance)

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

Extract($args);

Дальше мы установим заголовок и другие параметры для нашего виджета, которые могут быть изменены пользователем в меню видежета. Мы также добавляем специальные переменные типа $before_widget, $after_widget . Эти параметры обрабатываются темой.

$title = apply_filters("widget_title", $instance["title"]); $name = $instance["name"]; $show_info = isset($instance["show_info"]) ? $instance["show_info"] : false; echo $before_widget; // Display the widget title if ($title) echo $before_title . $title . $after_title; //Display the name if ($name) printf("

" . __("Hey their Sailor! My name is %1$s.", "example") . "

", $name); if ($show_info) printf($name); echo $after_widget;

Шаг 4. function update()

Function update($new_instance, $old_instance) { $instance = $old_instance; //Strip tags from title and name to remove HTML $instance["title"] = strip_tags($new_instance["title"]); $instance["name"] = strip_tags($new_instance["name"]); $instance["show_info"] = $new_instance["show_info"]; return $instance; }

Одна вещь, на которую нужно обратить внимание: здесь мы используем strip_tags , чтобы удалить из текста весь XHTML , который может нарушить работу нашего виджета.

Шаг 5. function form()

На следующем шаге мы создадим форму , которая послужит блоком ввода . Она примет определяемые пользователем настройки и значения. Функция form может включать чекбоксы, поля для ввода текста и т.д.

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

//Set up some default widget settings. $defaults = array("title" => __("Example", "example"), "name" => __("Bilal Shaheen", "example"), "show_info" => true); $instance = wp_parse_args((array) $instance, $defaults); ?>

Теперь мы создадим текстовое поле ввода . Мы заключим эти значения в тег абзаца.

// Widget Title: Text Input

" name="get_field_name("title"); ?>" value="" style="width:100%;" />

//Text Input

" name="get_field_name("name"); ?>" value="" style="width:100%;" />

// Checkbox

id="get_field_id("show_info"); ?>" name="get_field_name("show_info"); ?>" />

Заключение

Ну вот и все. Вы только что сделали самостоятельно симпатичный и простой виджет, который показывает имя автора блога. Более того, он дает возможность пользователю выбирать, показывать информацию аудитории или нет. Сохраните код в PHP файл и загрузите в папку своей темы. Вызовите его в вашем functions.php . После этого, перейдите в консоли в Внешний вид → Виджеты и вы увидите ваш виджет.

Весь этот код включен в прикрепленный к статье файл, так что скопировать и вставить можно еще проще. Наслаждайтесь!

В современной матрице IT-технологий квалифицированный администратор - персона хоть и не всегда видимая, зато важная и весьма занятая. Отбиваясь от назойливых пользователей, он постоянно должен присматривать за ареалом своего цифрового мира - сайтами. Давай же поможем нашему админу и создадим полезный виджет, внимательно следящий за доступностью всех его ресурсов. И да, погоду этот виджет показывать не будет.

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

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

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

Ping? Нет, не знаю...

В Java есть замечательный класс InetAddress , имеющий в своем чреве не менее замечательный метод isReachable() . Этот метод проверяет доступность ресурса и принимает в качестве параметра тайм-аут, то есть время, по истечении которого не отвечающий ресурс считается недоступным:

If (InetAddress.getByName("www.сайт").isReachable(5000))... // Сайт доступен

Лаконично, не правда ли? Вся проблема в том, что этот код прекрасно работает в Windows, но в Android’е всегда возвращает false, даже если приложению дать разрешение на доступ в интернет. По непонятной причине для отправки ICMP-пакета (Echo-Request) требуется рут.

Мы же ничего требовать не будем и поступим следующим образом: будем подключаться к сайту по протоколу HTTP и смотреть на код ответа. Если получим код 200 (HTTP OK), значит, сайт жив и работает, в противном случае считаем, что что-то не так (сайт недоступен).

Разрешения

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

Первая строка запрашивает разрешение на доступ в интернет, тогда как вторая позволяет отслеживать состояние подключения к сети (в терминологии 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.


GUI

Здесь все просто - несколько полей ввода (EditText) да кнопка (Button). Вся эта красота представлена на рис. 3. Обработчик кнопки (см. Main.java) считывает содержимое полей (применяется коллекция ArrayList) и записывает их в файл общих настроек, используя метод setData описанного выше класса PingPref:

ArrayList sites = new ArrayList(4); pf = new PingPref(this); ... public void bPing_click(View v){ fillSites(); for (int i = 0; i < sites.size(); i++) pf.setData(i+1, sites.get(i), -1); startservice(); } public void fillSites(){ sites.clear(); sites.add(ed1.getText().toString()); ... } public void startservice(){ Intent i = new Intent(this, PingService.class); this.startService(i); }

В качестве первоначального статуса доступности сайта, как и условились, устанавливаем –1. В заключение происходит запуск фонового сервиса startService, подробнее о котором поговорим далее.


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

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

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

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

Private ArrayList sites = new ArrayList(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) в манифесте проекта:

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

Атрибуты 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. О том, как быстро и просто получить такое превью, читай врезку.

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

... />

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

Итак, от визуальной стороны виджета (см. рис. 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), помеченную фиолетовым цветом. Какой бы размер виджета ни был, логотип твоего любимого журнала, а также закругленные уголки искажаться не будут. Эта область задается с помощью линеек слева и сверху от изображения. Обрати внимание на рамку толщиной в один пиксель с черными полосами - именно эта информация будет добавлена к исходному изображению.

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

Результат работы сохраняется в формате 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[i]; 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.

Вместо заключения

Мы проделали большую работу, и наш виджет работает как часы - админ наверняка будет доволен. Но вот один вопрос так и остался не заданным: как остановить периодические запуски сервиса, если он больше не нужен? Спешу тебя обрадовать, в коде приложения (загляни на dvd.сайт или мой сайт) остановка уже реализована с помощью специального флага setStopServiceFlag, срабатывающего в момент нажатия кнопки «Назад» в главной активности приложения (на кнопку «Домой» не распространяется). Обязательно изучи этот вопрос - это и будет твоим домашним заданием.

Превью для виджета

В Play Market живет мегаполезное приложение для разработчиков виджетов - Widget Preview, которое позволяет встроить в себя любой зарегистрированный в системе виджет, после чего делает его снимок. Результат можно сохранить в файл или отправить по почте.