Создать свой виджет для андроид. Как создать свой собственный виджет в WordPress? Что будем делать

май 21 , 2016

Каждый раз подключая на своих сайтах готовое решение от сервисов вроде disqus или google-аналитики, я удивляюсь простоте интеграции достаточно сложного функционала. Поставил в код 3 строчки javascript-кода - и у тебя уже развернулся блок с комментариями. Еще 2 строчки - и доступна аналитика от гугла или яндекса. Конечно, никакого волшебства при этом не происходит, те самые 3 строчки кода подтягивают с удаленного сервера весь нужный код, и по сути разворачивают небольшое веб-приложение на страницах Вашего сайта. Но как это устроено внутри и как это сделать самому? Разобраться с этим было достаточно интересно, и в итоге у меня получился небольшой виджет, который работает именно по такой простой схеме встраивания и при этом еще выполняют некоторые полезные вещи. Далее подробности.

Зачем нужны виджеты?

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

В этой статье на игрушку мы замахиваться не будем, а рассмотрим, как создать встраиваемый виджет для определения погоды. Конечно, для этого есть много различных погодных сервисов, но нам неинтересно воспользоваться готовым решением. Хочется создать простенький виджет своими руками, чтобы понять, как это вообще работает и в дальнейшем применить знания уже для каких-то полезных вещей. Заодно попутно освежим знания в нативном javascript и php.

Что будем делать?

Мы создадим виджет, который рисует форму с прогнозом погоды на завтра. В качестве примера возьмем только температуру, но как увидите дальше, легко можно будет добавить и другие данные. В виджете будет располагаться информация о температуре на завтра, раскрывающийся список с возможностью выбрать город. Кнопка Обновить будет отправлять запрос на сервер, который в свою очередь обращается на сторонний сервис для определения погоды и возвращает нам нужную инфу. Эти данные мы обрабатываем и возвращаем в браузер клиенту. Где уже клиентская часть виджета перехватывает информацию и выводит ее пользователю.


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

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

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

Здесь нужно небольшое уточнение: смысл виджетов в том, чтобы предоставлять уникальный, удобный, интересный функционал. Мы не даем в нашем примере какую-то особенную информацию. Погоду можно узнать где угодно. Но в познавательных целях, так как мы сами пока ничего не умеем делать, возьмем данные по погоде с сервиса Прогноз погоды в России и СНГ . Они любезно предоставляют бесплатный api, коим мы и воспользуемся.

По технологиям

Тоже ничего сложного. Про iframe я уже проговорился. javascript-код будем писать на vanillaJS, без единой дополнительной библиотеки. Даже без jQuery для манипуляции DOM. Наша цель еще состоит и в том, чтобы полученный виджет был максимально легковесным, и ни к чему тащить в браузер пользователя мешок дополнительных библиотек для облегчения своей работы. Поэтому вспомним, как работать с DOM и отправлять ajax-запросы на нативном javascript, и наши клиенты скажут нам спасибо. Серверная часть - пара десятков строк кода на php.

Пишем код. Создаем базовую страницу

Страницу, на которой будет располагаться наш будущий виджет. Напишем там пару строчек текста о том, какие мы классные в плане умения прогнозировать погоду на завтра. Это будет файл index.html - обычная html-страничка. В секции head напишем так:

Webdevkin. Демонстрация виджета Погода

В секции body вот так:

Что здесь из интересного? В файле css/style.css будем хранить стили для страницы (не для виджета!). И в нужном месте создаем iframe..html. Мы убираем у него границу и возможность скроллинга и задаем руками размеры виджета. Конечно, интереснее создать адаптивный, но до этого доберемся позже.

Стили для страницы, без особых изысков:

Body { font-family: Arial; font-size: 14px; } .wrapper { width: 960px; margin: 0 auto; } .weather-widget-container { margin: 30px 0; }

Заготовка для виджета

Виджет представляет из себя обычный html-документ. В чем отличие? Первое - мы не будем заморачиваться с лишними мета-тегами, потому как поисковикам наш виджет не нужен. И второе - и стили, и js-код мы напишем прямо в коде html-страницы. Нам это нужно, чтобы сделать загрузку виджета максимально быстрой, а уменьшение числа запросов к веб-серверу в этом нам поможет. Все, что нужно, мы загрузим одним файлом.

В секции head у нас будет такой код:

В секции body:

Рассмотрим эти блоки подробнее.

Разметка для виджета

Температура на завтра °C

Здесь обычная форма. Сверху небольшая панель, где мы будем выводить температуру. Дальше select с выбором города. Значения value у городов взяты с сервиса meteoservice.ru, где именно, расскажу позже, в разделе, где будем непосредственно получать данные о погоде. И в конце кнопка Обновить. Верстка у нас будет на классах, айдишники проставлены тем элементам, доступ к которым нужен из кода javascript. Префикс wbd- (от webdevkin) используется для удобства, чтобы не путать разметки виджета и основной страницы.

Стили для виджета

html, body, body * { margin: 0; padding: 0; } .wbd-widget, .wbd-widget * { box-sizing: border-box; font-family: Ubuntu; } .wbd-widget { border: solid 1px #333; padding: 20px; color: black; } .wbd-widget__info { color: steelblue; font-size: 16px; text-align: center; } .wbd-widget__form-label { display: block; line-height: 30px; }

Стили для виджета максимально просты - Вы можете написать их под себя, как угодно. Не забываем, что стили ставим прямо в html-файл виджета в раздел head - style.

javascript-код виджета.

Напишем класс, в котором будут методы инициализации, привязки событий и получения данных с сервера. Не забываем, что код будем писать на нативном js, так как не хотим обременять пользователей лишним грузом библиотек.

Var WeatherWidget = (function() { // Для выполнения ajax-запросов var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest: XDomainRequest; // Конструктор виджета function Widget() { this..php"; this.ui = { updateBtn: null, citySelect: null, temperatureSpan: null }; this.init(); } // Обновление данных о погоде Widget.prototype._updateData = function(e) { // Отправка ajax-запроса на сервер } // Инициализация компонентов ui Widget.prototype._initUI = function() { this.ui.updateBtn = document.getElementById("wbd-widget-update"); this.ui.citySelect = document.getElementById("wbd-widget-city"); this.ui.temperatureSpan = document.getElementById("wbd-widget-temperature"); } // Привязывание событий Widget.prototype._bindHandlers = function() { this.ui.updateBtn.addEventListener("click", Widget.prototype._updateData.bind(this)); } // Инициализация виджета Widget.prototype.init = function() { this._initUI(); this._bindHandlers(); this._updateData(); } // Возвращаем класс виджета return Widget; })(); new WeatherWidget();

Что здесь происходит? Мы создаем модуль на основе замыкания WeatherWidget, внутри оного пишем класс Widget, в прототип которого закидываем нужные методы и возвращаем его из замыкания. Последней строкой мы создаем экземпляр виджета - new WeatherWidget();

Переменная XHR нужна для выполнения ajax-запросов. В конструкторе инициализируем свойство url, по которому будем получать данные о погоде. Также есть объект ui, в котором хранятся 3 поля - те самые элементы DOM, к которым нам нужен доступ из js-кода. Инициализируются они позже, в методе _initUI(). Метод init() вызывает инициализацию ui, привязку событий и обновление данных с сервера (мы же хотим, чтобы при отрисовке виджета пользователь сразу видел погоду без нажатия на Обновить). В _bindHandlers привязывается метод _updateData к клику на кнопку Обновить. Это единственная "интерактивная" часть нашего виджета.

Обратите внимание на конструкцию Widget.prototype._updateData.bind(this). Здесь явным образом привязываем контекст - нам нужно, чтобы в методе _updateData this всегда указывал на экземпляр класса Widget. Сейчас будет видно, зачем - рассмотрим код метода _updateData

Получение данных с сервера, метод _updateData

// Обновление данных о погоде Widget.prototype._updateData = function(e) { e && e.preventDefault(); var xhr = new XHR(), city = this.ui.citySelect.value, temperatureSpan = this.ui.temperatureSpan, data = "city=" + city, resp; xhr.open("POST", this.url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(data); xhr.onreadystatechange = function() { if (this.readyState != 4) return; if (this.status != 200) { console.log("Request error"); return; } resp = JSON.parse(this.responseText); temperatureSpan.innerHTML = resp.temperature; } }

Обращаю внимание, что в параметры метода передаем свойство e (event). Оно нужно, чтобы сделать preventDefault в самом начале, дабы не перезагрузить наш виджет (иначе сработает submit формы).
Но делаем мы это такой строчкой
e && e.preventDefault();
Эта дополнительная проверка на наличие параметра e нужна, потому что когда мы вызываем this._updateData(), параметр e будет равен undefined, и соответственно e.preventDefault() выкинет исключение.

После этого мы создаем нужные переменные, объект для ajax-запроса, выбранный город и DOM-элемент, в который выводим значение температуры, полученной с сервера. data будет отправляться в ajax-запросе, resp - это ответ от сервера. Дальше мы открываем соединение и отправляем данные на сервер. Дожидаемся ответа 200 от сервера и выводим полученную температуру в span. Условимся, что от сервера приходит json-строка в формате {"code": "success", "temperature": "диапазон температур"}. Традиционно обработку ошибок оставлю на Вашу совесть, все делают по-разному. И наконец, нам осталось узнать, как получить данные о погоде с нашего сервера.

Как узнавать погоду?

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

Пишем php-код для получения информации о погоде

Как брать данные, уже понятно, алгоритм прост: вытаскиваем из $_POST параметр city - выбранный город, формируем строку вида http://xml.meteoservice.ru/export/gismeteo/point/$cityId.xml. Это путь к файлу с погодой. Отправляем на него get-запрос curl-ом. Полученный ответ парсим и вытаскиваем из него нужные параметры: минимальная и максимальная температуры. Полученные числа отдаем в json-объекте обратно в браузер.

$cityId = (int)$_POST["city"]; $url = "http://xml.meteoservice.ru/export/gismeteo/point/$cityId.xml"; $temperature = ""; if ($curl = curl_init()) { curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($curl); curl_close($curl); $xml = simplexml_load_string($result); $object = $xml->REPORT->TOWN->FORECAST->TEMPERATURE; $temperature = $object["min"] . "-" . $object["max"]; } echo json_encode(array("code" => "success", "temperature" => $temperature));

Поясню про simplexml_load_string - эта функция создает xml-объект, который мы уже можем парсить. А создается объект из строки xml, которую мы получаем от сервиса погоды.
REPORT->TOWN->FORECAST->TEMPERATURE - это путь по узлам xml, чтобы добраться до нужных значений температуры. Пройдите по ссылке выше (московская погода) и наглядно увидите эти пути.
Если что-то непонятно, спрашивайте в комментариях.

Итого

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

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

На прошлом уроке вы научились создавать свой собственный сайдбар , а на этом уроке вы узнаете как создать свой виджет на 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 - плагин формы обратной связи

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

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

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

Что собой представляет WordPress-виджет?

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

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

Создание виджета в WordPress

Код виджета можно оформить двумя способами: либо добавить его в файл functions.php вашей темы, либо создать отдельный функциональный плагин.

В данном руководстве мы создадим простой виджет, который просто приветствует посетителей. Скопируйте следующий код и поместите его в плагин или в functions.php вашей темы.

// Creating the widget class wpb_widget extends WP_Widget { function __construct() { parent::__construct(// Base ID of your widget "wpb_widget", // Widget name will appear in UI __("WPBeginner Widget", "wpb_widget_domain"), // Widget description array("description" => __("Sample widget based on WPBeginner Tutorial", "wpb_widget_domain"),)); } // Creating widget front-end // This is where the action happens public function widget($args, $instance) { $title = apply_filters("widget_title", $instance["title"]); // before and after widget arguments are defined by themes echo $args["before_widget"]; if (! empty($title)) echo $args["before_title"] . $title . $args["after_title"]; // This is where you run the code and display the output echo __("Hello, World!", "wpb_widget_domain"); echo $args["after_widget"]; } // Widget Backend public function form($instance) { if (isset($instance[ "title" ])) { $title = $instance[ "title" ]; } else { $title = __("New title", "wpb_widget_domain"); } // Widget admin form ?>

" name="get_field_name("title"); ?>" type="text" value="" />

Теперь перейдите в раздел Внешний вид – Виджеты и перетащите виджет WPBeginner Widget в свой сайдбар, чтобы увидеть его в действии.

Довольно просто, не правда ли? Сначала мы создаем произвольный виджет. Затем мы определяем, что именно виджет делает и как он отображается в бэкенде. После чего мы определяем, как следует обрабатывать изменения, сделанные виджетом. Наконец, мы регистрируем и загружаем виджет.

В этом коде некоторые вещи могут вызвать у вас вопросы. Скажем, к примеру, что делает wpb_text_domain. WordPress использует gettext для обработки трансляции и локализации. Так вот: wpb_text_domain и __e указывают gettext на то, что эти строки доступны для перевода. , смотрите в нашем руководстве.

В современной матрице 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, которое позволяет встроить в себя любой зарегистрированный в системе виджет, после чего делает его снимок. Результат можно сохранить в файл или отправить по почте.

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

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

Система виджетов WordPress

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

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

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

“Движок” WordPress поставляется с целым набором различных виджетов. Но часто их функциональности бывает недостаточно для решения тех или иных задач. Поэтому и возникает необходимость создать свой, который будет отвечать всем требованиям.

Создание виджетов в WordPress подразумевает наличие навыков программирования на популярном скриптовом языке PHP , предназначенного для веб-разработки. В этом материале мы не будем детально рассматривать синтаксис языка, так как это уже отдельная тема. Мы лишь коснемся тех его конструкций, которые необходимы для успешного создания виджета.

В системе WordPress создать виджет можно двумя способами. Первый заключается в создании отдельного плагина, после активации которого будет доступен виджет для использования. Мы же воспользуемся более легким способом – добавим код виджета непосредственно в основной конфигурационный файл темы – functions.php .

Если Вы не знаете, как редактировать functions.php , то в этом случае Вам поможет плагин ProFunctions .

Разработка виджета

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

Для создания виджетов в WordPress применяется объектная модель программирования языка PHP . В “движке” имеются все необходимые инструменты для этого. Так, для виджетов существует специальный класс WP_Widget , использование которого и позволит успешно создать необходимый виджет для сайта WordPress. Он включает около 20 различных методов. Однако нам потребуются лишь четыре из них:

  • __construct() – регистрация базовой информации о виджете;
  • widget() – вывод виджета в области виджетов на сайте;
  • form() – параметры виджета, отображаемые в области администрирования WordPress;
  • update() – обновление настроек виджета в админ-панели.

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

Рассмотрим поэтапно создание нашего виджета.

Во-первых, необходимо создать свой класс, который будет наследовать вышеуказанные методы основного класса WP_Widget .

Class wpschool_example_widget extends WP_Widget { }

В этом коде наш класс имеет название wpschool_example_widget . Директивой extends мы даем понять системе, что он будет наследовать все методы основного класса WP_Widget .

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

Public function __construct() { $widget_options = array("classname" => "wpschool_widget", "description" => "Это наш первый виджет",); parent::__construct("wpschool_widget", "WPSchool Widget", $widget_options); }

Чтобы понять эту функцию, следует начать с строки, начинающейся с parent::__ construct() . Эта строка создает новый виджет с идентификатором wpschool_widget , именем WPSchool Widget и двумя параметрами виджетов: имя класса (wpschool_widget ) и краткое описание (Это наш первый виджет ).

Весь этот код входит в wpschool_example_widget и используется для регистрации виджета в WordPress, а затем отображает заголовок и описание виджета в области администратора.

Следующий метод, который мы добавим, будет widget() . Он позволит вывести виджет в области виджетов на сайте.

Public function widget($args, $instance) { $title = apply_filters("widget_title", $instance[ "title" ]); $blog_title = get_bloginfo("name"); $tagline = get_bloginfo("description"); echo $args["before_widget"] . $args["before_title"] . $title . $args["after_title"]; ?>

Site Name:

Tagline:

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

Также следует понять еще кое-какие моменты.

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

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

Фильтр widget_title возвращает заголовок текущего экземпляра виджета.

get_bloginfo() – функция, которая может использоваться для возврата всех видов метаданных WordPress-сайт, включая его название и описание.

После определения нескольких переменных, используя информацию из приведенного выше списка, код в примере продолжает формировать фактический вывод контента, который состоит из информации из переменной $args , заголовка, названия и описания сайта.

Обратите внимание, что практически каждый виджет должен включать аргументы before_widget , after_widget , before_title и after_title . Они необходимы для обеспечения того, чтобы каждый виджет был вложен в специфичные для темы теги HTML .

На следующем этапе добавим метод form() , позволяющий сохранять параметры виджета, отображаемые в админ-панели WordPress.

Public function form($instance) { $title = ! empty($instance["title"]) ? $instance["title"] : ""; ?>

" name="get_field_name("title"); ?>" value="" />

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

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

Следующий на очереди – метод update() , отвечающий за обновление настроек виджета в админ-панели.

Public function update($new_instance, $old_instance) { $instance = $old_instance; $instance[ "title" ] = strip_tags($new_instance[ "title" ]); return $instance; }

Видно, что метод принимает два параметра: $new_instance и $old_instance . Первый содержит значения, добавленные в форму настроек виджетов. Второй содержит существующие настройки (если таковые существуют).

Метод update() должен соответствующим образом проверять новые параметры, а затем назначать их переменной $instance и возвращать эту обновленную переменную.

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

Function wpschool_register_widget() { register_widget("wpschool_example_widget"); } add_action("widgets_init", "wpschool_register_widget");

Тут, во-первых, создается функция для регистрации виджета, используя имя объекта виджета для его идентификации. Затем идет привязка функции регистрации к WordPress, используя хук widgets_init .

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

После добавления всего кода в файл functions.php в админ-разделе Внешний вид -> Виджеты должен появится виджет с названием WPSchool Widget , который теперь доступен для добавления на панель виджетов Вашей темы.

Полный текст нашего виджета:

/* Виджет WPSchool Widget */ class wpschool_example_widget extends WP_Widget { // Установка идентификатора, заголовка, имени класса и описания для виджета. public function __construct() { $widget_options = array("classname" => "wpschool_widget", "description" => "Это наш первый виджет",); parent::__construct("wpschool_widget", "WPSchool Widget", $widget_options); } // Вывод виджета в области виджетов на сайте. public function widget($args, $instance) { $title = apply_filters("widget_title", $instance[ "title" ]); $blog_title = get_bloginfo("name"); $tagline = get_bloginfo("description"); echo $args["before_widget"] . $args["before_title"] . $title . $args["after_title"]; ?>

Site Name:

Tagline:

" name="get_field_name("title"); ?>" value="" />