Pull to refresh
VK
Building the Internet

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

Reading time 6 min
Views 43K
На Хабре уже достаточно статей о том, как разработать «hello world»-виджет для устройств на базе Android. Еще больше об этом можно почитать в сети, в том числе и на сайте Google для разработчиков, StackOverflow и других ресурсах. Казалось бы, все подробно разжевано, есть сотни примеров — зачем же писать очередную статью, когда вокруг и так достаточно информации?
Однако, когда мы начали разработку виджета, нам пришлось потратить несколько недель на то, чтобы разобраться с нюансами и реализовать проект так, как мы задумали его изначально.
Надеемся, наш опыт поможет сэкономить время на реализацию вашего виджета.

Подготовка


Для разработки была выбрана Android Stuido.Продукт еще очень сырой, не все разработчики готовы на него перейти, но отличная работа Preview и широкие возможности системы сборки Gradle берут верх над всеми недочетами. Поэтому мы рискнули попробовать, и, как оказалось, не зря.

Для тестирования, помимо непосредственной отладки на тестовом смартфоне, мы также использовали программные эмуляторы. Стандартным пользоваться достаточно проблематично, были рассмотрены различные высокопроизводительные эмуляторы Android-x86, AndroVM, Genymotion, Manymo и другие. В итоге мы выбрали Genymotion — он подкупил своей простотой установки и скоростью работы Android-x86, подробная инструкция по настройке и установке — необходим для тестирования на устройствах с Android 4.0 и ниже.

Данные эмуляторы отлично работают под различными ОС: Linux, Mac, Windows, у разработчиков бывают разные предпочтения, а переубеждать их неправильно, так что кроссплатформенные инструменты выручают.

Также эти программы помогают при автоматизированном тестировании: тесты написаны с использованием Android Instrumentation Framework, JUnit, Robotium. Подробнее об этом в следующей статье, которую мы опубликуем в ближайшее время :)

Проектирование


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


По данным Google Play, в мире зарегистрировано около 4500 видов различных устройств с поддержкой Android.


Помимо разрешения экрана, эти устройства могут различаться диагоналями и плотностью точек на единицу площади (ppi). К счастью, задачу можно упростить и для определения размеров элементов виджета использовать аппаратно-независимые пиксели — dp. Большинство смартфонов используют сетку 4x4, для 7-дюймовых планшетов сетка может быть 6x6, да еще и сам размер ячейки зависит от лаунчера и версий API Android. В таблице мы привели получившиеся размеры в dp для различных устройств:
Samsung GT-i9000 Nexus 4 Samsung Tab Nexus 7
1 x 1 64 x 58 64 x 58 74 x 74 80 x 71
2 x 2 144 x 132 152 x 132 148 x 148 176 x 159
4 x 3 304 x 206 328 x 206 296 x 222 368 x 247

Можно отталкиваться от формул:
для API младше 14 размер = (74 x количество ячеек) — 2
для последних версий размер = (70 x количество ячеек) — 30

Если во время тестирования вы сталкиваетесь с проблемами на каком-то конкретном устройстве, например при смене ориентации экрана, то проще добавить отдельный layout или указать нужный размер в dimens.xml, чем пытаться подогнать параметры. Еще на этапе проектирования обратите внимание на повторно используемые элементы, чтобы при разработке вынести их в отдельные layout, а для вставки в необходимое место используйте Include. В нашем приложении данную технологию использовали для новостей, и для реализации тени у некоторых элементов home_news_row.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_row"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include
        android:id="@+id/news_item1"
        layout="@layout/home_news_item"/>
    <include
        android:id="@+id/news_item2"
        layout="@layout/home_news_item"/>
</LinearLayout>

home_news_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/news_img"
        android:scaleType="centerCrop"
        android:layout_weight="1"/>
    <TextView
        android:id="@+id/news_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="13sp"/>
</LinearLayout>

Реализация


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

Проанализировав текущее распространение версий Android мы выяснили, что версия 2.2 все еще актуальна и ее необходимо поддерживать. К сожалению, поддержка изменения размеров виджета доступна только с версии 3.0, поэтому для более старых версий сделаем статичную версию развернутого виджета. Доля устройств версий 3.x на текущий момент несущественна, и мы решили реагировать на изменение размера виджета начиная с Android 4.1 c помощью метода onAppWidgetOptionsChanged. Но не все с ним гладко: он не срабатывает в некоторых модифицированных прошивках. Пришлось искать альтернативное решение, и оно нашлось: мы использовали событие com.sec.android.widgetapp.APPWIDGET_RESIZE в методе onReceive():
public void onReceive(Context context, Intent intent) {
        if (intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
            handleResize(context, intent);
        }

        super.onReceive(context, intent);
    }

    private void handleResize(Context context, Intent intent) {
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        Bundle newOptions = getOptions(context, intent);
        int appWidgetId = intent.getIntExtra("widgetId", 0);

        if (!newOptions.isEmpty()) {
            onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        }
    }

    public Bundle getOptions(Context context, Intent intent) {
        Bundle newOptions = new Bundle();

        int appWidgetId = intent.getIntExtra("widgetId", 0);
        int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
        int widgetSpanY = intent.getIntExtra("widgetspany", 0);

        if(appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
            newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74);
            newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74);
        }
        return newOptions;
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
	Log.d("height", newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
	Log.d("width", newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
}

При установке виджета на домашний экран, пользователь может выбрать в настройках цвет и прозрачность. В данной реализации нет нечего сложного, но есть один нюанс: уровень прозрачности необходимо добавить к выбранному цвету. Например, вот так это реализовано у нас:
int color = Color.parseColor(“#FFFFFF”);
int transparent =150;
color = Color.argb(transparent, Color.red(color), Color.green(color), Color.blue(color));

Полученный цвет с уровнем прозрачности применяется к элементу виджета. В нашем случае мы просто устанавливаем setBackgroundColor() у LinearLayout.

Также бывают ситуации, когда в альбомном режиме размер ячейки виджета получается меньше, чем в портретном, в связи с чем текст заданной длины уже не помещается. Можно попробовать уменьшить размер текста, но на устройствах с низким разрешением он становится нечитаемым. В связи с этим при смене ориентации мы просто уменьшаем в layout альбомного режима количество выводимых строк text.setMaxLines(2), а размер шрифта оставляем прежним:
android:maxLines="3"
android:ellipsize="end"

Последнее свойство добавляет в конце строки многоточие.

Для того, чтобы наш виджет было легче найти в списке установленных, нужен последний штрих: подготовка картинок-превью, или previewImage. Можно попытаться воспроизвести итоговый виджет в графическом редакторе, мы же воспользовались приложением Widget Preview.

Вот что получилось в итоге:



Скачать итоговое приложение можно тут.

Спасибо за внимание!
Tags:
Hubs:
+45
Comments 20
Comments Comments 20

Articles

Information

Website
vk.com
Registered
Founded
Employees
5,001–10,000 employees
Location
Россия
Representative
Миша Берггрен