company_banner

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

    На Хабре уже достаточно статей о том, как разработать «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.

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



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

    Спасибо за внимание!
    Mail.Ru Group 767,35
    Строим Интернет
    Поделиться публикацией
    Комментарии 20
    • +16
      Приятно все чаще видеть в блоге mail.ru технические посты, а не маркетинговые.
      • 0
        На то и расчет :)
        • 0
          Я думаю, это тот самый случай, когда цели общества и компании совпадают. И это замечательно.
      • 0
        В 13 идее тоже классный Preview и поддержка Gradle. Думал уже переходить на студию, но обновив идею, передумал.
        • –1
          Ребята, что это? Я вижу минусы? Intellij Idea 13 community + Android SDK = Android Studio. Я в андроиде ковыряюсь недавно, тонкости мог упустить, но для себя смысла переходить пока не вижу. Preview при редактировании XML отличный, Gradle мне не нужен, но поддерживается, среда работает на голову шустрее студии. Моё мнение таково
        • +3
          Gradle нереально долго собирает проекты, по сравнению с Ant разница раз в 10. Я не углублялся в суть проблемы, просто из коробки запустил сирый SDK c эклипсом и новый с Android Studio. Я не являюсь профильным Андроид разработчиком, иногда кое-чего сделать нужно.
          И судя по форумам проблема не только у меня такая.
          • 0
            Есть такое. Плюс на слабом ноуте студия Gradle-проект еле ворочала. Поэтому пока использую Ant.
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Разработчики ADS знают про эту проблему — решают.
                • 0
                  Та же проблема со скоростью, но думаю это решится. У меня тяжко собирает первые разы, потом скорость увеличивается и уже можно сравнивать с Ant'ом. Android Studio и Gradle еще очень сырые, не даром студия еще в состоянии беты. Но в общем, работать уже можно, и все «прелести» студии оправдывают жертвы в скорости сборки.
                • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  Спасибо за статью, пригодится.
                  • +2
                    Молодцы. Класная статья. Обязательно буду ждать статью о Unit-тестировании, с обзором фреймворков. Сам сейчас этим озадачен.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • 0
                        А что за версия android? как я понимаю это кастомная прошивка? можно подробности?
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • +1
                            Да в практически любых лаунчерах можно так сделать.
                            • 0
                              Спасибо в ближайшем релизе исправим.

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

                        Самое читаемое