Андроид виджет: Скачать виджеты для Андроид

Содержание

Виджеты на Android. Редкая фича, в которой придется разобраться / Хабр

Привет, Хабр! Меня зовут Александр Хакимов, я android-разработчик в компании FINCH.

У вас бывало такое, что ваш дизайн был под iOS, а вам приходится адаптировать его под android? Если да, то часто ли ваши дизайнеры используют виджеты? К сожалению, виджет — редкий кейс для многих разработчиков, потому что с ним редко кто работает,

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

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

Для создания виджета нужно знать:

  1. Особенности компонентов виджета.
  2. Особенности отображения виджета в сетке экрана.
  3. Особенности обновления виджета.

Разберем каждый пункт отдельно.

Особенности компонентов виджета

С этим пунктом знаком любой разработчик, который хоть раз работал с RemoteViews. Если вы из таких, смело переходите к следующему пункту.

RemoteViews предназначен для описания и управления иерархиями Views, которые принадлежат процессу другого приложения. С помощью управления иерархиями можно изменять свойства или вызывать методы, принадлежащие View, которое выступает частью другого приложения. В RemoteViews входит ограниченный набор компонентов стандартной библиотеки компонентов android.widget.

View внутри виджетов работают в отдельном процессе (как правило, это домашний экран), поэтому для изменения UI виджета используется расширение BroadcastReceiver — AppWidgetProvider, работающий в нашем приложении.

Особенности отображения виджета в «сетке» экрана

На самом деле, этот пункт не такой уж и сложный, если заглянуть в официальные гайдлайны:

Each widget must define a minWidth and minHeight, indicating the minimum amount of space it should consume by default. When users add a widget to their Home screen, it will generally occupy more than the minimum width and height you specify. Android Home screens offer users a grid of available spaces into which they can place widgets and icons. This grid can vary by a device; for example, many handsets offer a 4×4 grid, and tablets can offer a larger, 8×7 grid.

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

Пример настроек виджета при создании в Android Studio

Виджет, который добавили на на Home screen, обычно займет больше места чем минимальные ширина и высота экрана, которые вы задали. Android Home screens предоставляет пользователям сетку доступного пространств, в которых могут быть расположены виджеты и иконки. Эта сетка может отличаться в зависимости от устройства; например, многие телефоны предлагают сетку 4х4, а планшеты могут предложить большие сетки 8х7.

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

Минимальные ширину и высоту виджета для заданного количества столбцов и строк можно вычислить по формуле:

minSideSizeDp = 70 × n − 30, где n —количество строк или столбцов

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

Особенности обновления виджета

Так как AppWidgetProvider по своей сути является расширением BroadcastReceiver-а, с ним можно делать все то же самое, что и с обычным BroadcastReceiver. AppWidgetProvider просто парсит соответствующие поля из Intent, полученного в onReceive и вызывает методы перехвата с полученными extras.

Сложность возникла с частотой обновления контента — все дело в разнице внутренней работы виджетов на iOS и Android. Дело в том, что данные на iOS-виджетах обновляются тогда, когда виджет становится виден пользователю. В Android, такого события не существует. Мы не можем узнать, когда пользователь видит виджет.

Для виджетов на Android рекомендуемым способом обновления является обновление по таймеру. Настройки таймера задаются параметром виджета updatePeriodMillis. К сожалению, эта настройка не позволяет обновлять виджет чаще чем раз в 30 минут. Ниже я расскажу об этом подробнее.

Кейс создания виджета

Дальше речь пойдет о кейсе который был у нас в FINCH в крупном лотерейном приложении с приложением «Столото» для участия в государственных лотереях.

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

В нашем кейсе дизайн виджета предусматривал два состояния:

  • Для авторизованного пользователя
  • Для неавторизованного пользователя

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

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

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

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

override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        injector.openScope(this, *arrayOf(this))
        // update each of the widgets with the remote adapter
        appWidgetIds
            .forEach {
                updateWidget(context, appWidgetManager, it)
          }
    }

Пишем апдейт для каждого инстанса нашего виджета.

private fun updateWidget(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int
    ) {
//Создаем remoteViews для текущего widgetId
        val remoteViews = RemoteViews(
            context.packageName,
            R.layout.app_widget_layout
...
// делаем что то не интересное для этой статьи
...
// Обновляем адаптер для remoteViews
updateRemoteAdapter(context, remoteViews, appWidgetId)
 
//Обновляем виджет созданным remoteViews 
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
//Обновляем collection view этого виджета
appWidgetManager.notifyAppWidgetViewDataChanged(
            appWidgetId,
            R.id.lvWidgetItems
        )
    }

Обновляем адаптер.

private fun updateRemoteAdapter(context: Context, remoteViews: RemoteViews, appWidgetId: Int) {
//Назначаем свою реализацию RemoteViewsService на роль RemoteAdapter для нашего списка
        val adapterIntent = Intent(context, StolotoAppWidgetRemoteViewsService::class.java).apply {
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        }
        remoteViews.setRemoteAdapter(R.id.lvWidgetItems, adapterIntent)
//Формируем actionIntent для pendingIntent  чтобы обрабатывать клики элементы виджета
        val actionIntent = Intent(context, StolotoAppWidgetProvider::class.java).apply {
            action = WIDGET_CLICK_ACTION
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        }
        val pendingIntent = PendingIntent.getBroadcast(
            context, 0, actionIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
//Назначаем pendingIntent шаблоном для клика по нашим айтемам
        remoteViews.setPendingIntentTemplate(R.id.lvWidgetItems, pendingIntent)
    }

Пишем реализацию нашего сервиса. В нем нам важно указать, какую реализацию интерфейса RemoteViewsService.RemoteViewsFactory использовать, чтобы генерировать контент.

class StolotoAppWidgetRemoteViewsService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
        StolotoAppWidgetRemoteViewsFactory(
            this.applicationContext,
            intent
        )
}

Фактически это тонкий wrapper над Adapter. Благодаря ему, мы можем связывать наши данные с remote collection view. RemoteViewsFactory предоставляет методы генерации RemoteViews для каждого элемента в наборе данных. У конструктора нет никаких требований — все что я делаю, это передаю в нем контекст.

Далее будет пару слов об основных методах:

  1. onCreate – создание адаптера.
  2. getLoadingView – метод предлагает возвращать View, которое система будет показывать вместо пунктов списка, пока они создаются. Если ничего здесь не создавать, то система использует некое дефолтное View.
  3. getViewAt – метод предлагает создать пункты списка. Здесь идет стандартное использование RemoteViews.
  4. onDataSetChanged вызывается, когда поступил запрос на обновление данных в списке. Т.е. в этом методе мы подготавливаем данные для списка. Метод заточен под выполнение тяжелого, долгого кода.
  5. onDestroy вызывается при удалении последнего списка, который использовал адаптер (один адаптер может использоваться несколькими списками).
  6. RemoteViewsFactory живет пока все инстансы списка живы, поэтому мы можем хранить в нем текущие данные, например, список текущих айтемов.

Определяем список данных, который будем показывать:

private val widgetItems = ArrayList<WidgetItem>()

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

override fun onCreate() {
        updateDataSync()
}

При вызове команды на обновление данных, так же вызываем updateDataSync()

   override fun onDataSetChanged() {
        updateDataSync()
    }

Внутри updateDataSync тоже все просто. Очищаем текущий список item-ов. Загружаем данные профиля и игры.

 private fun updateDataSync() {
        widgetItems.clear()
        updateProfileSync()
        updateGamesSync()
    }

Здесь уже поинтереснее

private fun updateProfileSync() {

Так как нам важно показывать профиль только авторизованному пользователю, то и информацию профиля нам нужно загружать только в этом случае:

val isUserFullAuth = isUserFullAuthInteractor
            .execute()
            .blockingGet()
        if (isUserFullAuth) {
            val profile = getWidgetProfileInteractor
                .execute()
                .onErrorReturn {
                    WidgetProfile()
//Возвращаем пустую модель профиля в случае ошибок или отсутствия корректных данных профиля
                }
                .blockingGet()

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

Для бизнес логики отсутствие данных кошелька является критичным, поэтому, в случае некорректного кошелька, модель профиля не будет создана и добавлена в список item-ов.

  if (profile.walletAmount >= 0L) {
                widgetItems.add(
                    WidgetItem.Profile(
                        wallet = profile.walletAmount.toMoneyFormat(),
                        waitingTickets = if (profile.waitingTicketsCount >= 0) profile.waitingTicketsCount.toString() else "",
                        unpaidPrizeAmount = if (profile.unpaidPrizeAmount >= 0) profile.unpaidPrizeAmoount.toMoneyFormat() else ""
                    )
                )
            }
        }
    }

Метод updateGamesSync() использует getWidgetGamesInteractor и добавляет в список widgetItems набор актуальных для виджета игр.

Прежде чем перейти к генерации карточек, рассмотрим подробнее модель WidgetItem. Она реализована через kotlin sealed class, что делает модель более гибкой, а работу с ней более удобной.

sealed class WidgetItem {
 
    data class Profile(
        val wallet: String,
        val waitingTickets: String,
        val unpaidPrizeAmount: String
    ) : WidgetItem()
 
    data class Game(
        val id: String,
        val iconId: Int,
        val prizeValue: String,
        val date: String
    ) : WidgetItem()
}

Создаем RemoteViews и определяем их отклик через FillInIntent

override fun getViewAt(position: Int): RemoteViews {
        return when (val item = widgetItems[position]) {
            is WidgetItem.Profile -> {
              RemoteViews(
                        context.packageName,
                        R.layout.item_widget_user_profile
                    ).apply {
                        setTextViewText(R.id.tvWidgetWalletMoney, item.wallet)
                        setTextViewText(R.id.tvWidgetUnpaidCount, item.unpaidPrizeAmount)
                        setTextViewText(R.id.tvWidgetWaitingCount, item.waitingTickets)
                        setOnClickFillInIntent(
                            R.id.llWidgetProfileWallet, Intent().putExtra(
                                StolotoAppWidgetProvider.KEY_PROFILE_OPTIONS,
                                StolotoAppWidgetProvider.VALUE_USER_WALLET
                            )
                        )
                        setOnClickFillInIntent(
                            R.id.llWidgetProfileUnpaid, Intent().putExtra(
                                StolotoAppWidgetProvider.KEY_PROFILE_OPTIONS,
                                StolotoAppWidgetProvider.VALUE_UNPAID_PRIZE
                            )
                        )
                        setOnClickFillInIntent(
                            R.id.llWidgetProfileWaiting, Intent().putExtra(
                                StolotoAppWidgetProvider.KEY_PROFILE_OPTIONS,
                                StolotoAppWidgetProvider.VALUE_WAITING_TICKETS
                            )
                        )
                    }
 
            is WidgetItem.Game -> {
                RemoteViews(
                    context.packageName,
                    R.layout.item_widget_game
                ).apply {
                    setImageViewResource(R.id.ivWidgetGame, item.iconId)
                    setTextViewText(R.id.tvWidgetGamePrize, item.prizeValue)
                    setTextViewText(R.id.tvWidgetGameDate, item.date)
                    setOnClickFillInIntent(
                        R.id.llWidgetGame, Intent().putExtra(
                            StolotoAppWidgetProvider.KEY_GAME_CLICK, item.id
                        )
                    )
                }
            }
        }
    }

Метод setOnClickFillInIntent назначает указанной viewId intent, который будет объединен с родительским PendingIntent для определения поведения при клике на view с этим viewId. Таким образом, мы сможем реагировать на клики пользователей в нашем WidgetProvider.

Ручное обновление виджета

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

Все изменилось когда «бизнес» обратил внимание, что когда пользователь смотрит на виджет, на нем отображаются неактуальные данные: «Вот на моем iPhone, я открываю виджет и там САМЫЕ свежие данные моего профиля».

Ситуация банальна: iOS генерирует новые карточки при КАЖДОМ показе виджетов, ведь для этого у них отведен специальный экран, а Android не имеет подобных событий для виджета в принципе. Пришлось учесть, что некоторые лотереи проводятся раз в 15 минут, поэтому виджет должен давать актуальную информацию – ты хочешь поучаствовать в каком-то тираже, а он уже прошел.

Чтобы выйти из этой неприятной ситуации и как то решить проблему с обновлением данных, мной было предложено и реализовано проверенное временем решение — кнопка «обновить».

Добавляем эту кнопку в макет layout-a со списком и инициализируем её поведение при вызове updateWidget

...
//Создадим Intent с дейстивем AppWidgetManager.ACTION_APPWIDGET_UPDATE
val intentUpdate = Intent(context, StolotoAppWidgetProvider::class.java)
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE

//Назначим обновление всем инстансам виджета
val ids = AppWidgetManager.getInstance(context)
   .getAppWidgetIds(ComponentName(context, StolotoAppWidgetProvider::class.java))
intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)

//Назначим этот intent как PendingIntent, используя PendingIntent.getBroadcast()
val pendingUpdate = PendingIntent.getBroadcast(
   context,
   appWidgetId,
   intentUpdate,
   PendingIntent.FLAG_UPDATE_CURRENT
)
//Назначим этот pendingIntent откликом на нажатие пользователем кнопки ‘Обновить’
remoteViews.setOnClickPendingIntent(R.id.ivWidgetRefresh, pendingUpdate)
…

Первые наработки показали грустную картину: от нажатия на кнопку «обновить» до фактического обновления, могло пройти несколько секунд. Хотя виджет и генерируется нашим приложением, он фактически находится во власти системы и общается с нашим приложением через broadcast-ы.

Т.е. при нажатии на кнопку «обновить» нашего виджета запускается цепочка:

  1. Получить Intent в onReceive провайдера action’ .
  2. AppWidgetManager.ACTION_APPWIDGET_UPDATE.
  3. Вызов onUpdate для всех указанных в intent-e widgetIds.
  4. Зайти в сеть за новыми данными.
  5. Обновить локальные данные и отобразить новые карточки списка.

В результате, обновление виджета выглядело не очень красиво, так как нажав на кнопку, мы пару секунд смотрели на тот же виджет. Было непонятно обновились ли данные. Как решить проблему визуального отклика?

Во-первых, я добавил флаг isWidgetLoading с глобальным доступом через интерактор. Роль этого параметра довольно проста — не показывать кнопку «обновить», пока идет загрузка данных виджета.

Во вторых, процесс загрузки данных в фабрике я разделил на три этапа:

enum class LoadingStep {
   START,
   MIDDLE,
   END
}

START — начало загрузки. На этом этапе состояние всех вьюшек адаптера и глобального флага загрузки меняется на «загружается».

MIDDLE — этап основной загрузки данных. После их загрузки глобальный флаг загрузки переводится в состояние «загружено», а в адаптере отображаются загруженные данные.

END — конец загрузки. Адаптеру на этом шаге не требуется изменять данные адаптера. Этот шаг нужен чтобы корректно обработать этап обновления вьюшек в WidgetProvider.

Давайте посмотрим подробнее как теперь выглядит обновление кнопки в провайдере:

if (isFullAuthorized && !widgetLoadingStateInteractor.isWidgetLoading) {
   remoteViews.setViewVisibility(R.id.ivWidgetRefresh, View.VISIBLE)
...
// инициализируем поведение кнопки так же, как в примере ранее
...   
} else {
   remoteViews.setViewVisibility(
       R.id.ivWidgetRefresh,
       if (isFullAuthorized) View.INVISIBLE else View.GONE // Оставляем место под кнопку для авторизованного пользователя.
   )
}

А теперь посмотрим на то, что происходит в адаптере:

private fun updateDataSync() {
   when (loadingStep) {
       START -> {
           widgetItems.forEach { it.isLoading = true }
           widgetLoadingStateInteractor.isWidgetLoading = true
           loadingStep = MIDDLE
           widgetManager.updateWidgets()
       }
       MIDDLE -> {
           widgetItems.clear()
           updateProfileSync()
           updateGamesSync()
           widgetLoadingStateInteractor.isWidgetLoading = false
           loadingStep = END
           widgetManager.updateWidgets()
       }
       END -> {
           loadingStep = START
       }
   }
}

Логика работы:

  1. В конце этапов START и MIDDLE я вызываю метод updateWidgets для того, чтобы обновить состояние view управляемых провайдером.
  2. После выполнения шага START для пользователя визуально отображается «загрузка» в ячейках виджета, и начнется этап MIDDLE.
  3. Перед тем как вызвать обновление данных адаптера на шаге MIDDLE, провайдер скроет кнопку «обновить».
  4. После выполнения шага MIDDLE, для пользователя будет отображаются новые данные и начнется этап END.
  5. Перед тем как вызвать обновление данных адаптера, на шаге END, провайдер скроет кнопку «обновить». С точки зрения фабрики все данные будут актуальными, поэтому на шаге END меняем значение loadingStep на START.


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

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

Удачи!

Что такое виджет на Андроид и как его добавить на рабочий стол


Что такое виджет на Андроид и как его добавить на рабочий стол

Что такое виджет на Андроид и как его добавить на рабочий стол. Одно из первых понятий, с которыми мы сталкиваемся, когда начинаем осваивать свой первый Android планшет или телефон – это виджет. Что же такое виджет?

Что такое виджет на Андроид и как его добавить на рабочий стол

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

Например, есть виджеты, с помощью которых можно отображать такие сведения, как загрузка процессора, состояние батареи, информацию о текущей погоде и прочем. Есть виджеты, с помощью которых можно быстро включить или выключить GPS, Wi-Fi, Bluetooth, динамики и управлять другим оборудованием Android устройства. Есть такие виджеты, как погодные информеры, которые отображают на экране информацию о текущей погоде и прогнозе погоды, и которые могут вызывать погодное приложение, частью которого они являются.

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

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

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

В Android версии 3.0 и выше добавить виджет можно с помощью достаточно нехитрых манипуляций:

1. Нажмите на значок в виде крестика, расположенный в правом верхнем углу экрана

2. В открывшемся окне, в нижней его части вы увидите списки установленных на планшет или телефон виджетов, ярлыков приложений, обоев и прочего. В верхней части окна расположен список рабочих столов.

3. Тапните по надписи «Виджеты» и выберите нужный виджет из списка ниже (список можно прокручивать вправо и влево). Виджет будет помещен на текущий рабочий стол.

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


В более ранних версиях Android установка виджетов на экран производится немного по-другому:

1. Нажмите пальцем и удерживайте на свободном месте экрана планшета или телефона.

2. В открывшемся меню выберите пункт «Виджеты»

3. Откроется список, с установленными на устройство виджетами.

4. Выберите нужный виджет из списка, тапнув по нему, и он будет помещен на экран вашего устройства.

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


 

Hello World widget для Android / Хабр

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

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

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

Widget provider info – Это xml файл, описывающий метаданные виджета. К ним относятся размер виджета, частота его обновления, файл шаблона и класс конфигурации. Вот так будет выглядить наш файл (res/xml/hello_widget_provider.xml):

  1. <?xml version=»1.0″ encoding=»utf-8″?>
  2. <appwidget-provider xmlns:android=»schemas.android.com/apk/res/android»
  3.     android:minWidth=»146dip»
  4.     android:minHeight=»72dip»
  5.     android:updatePeriodMillis=»86400000″
  6.     android:initialLayout=»@layout/main» />

Размер виджета можем указывать любой, но гугл рекомендует придерживаться формуле расчёта размера виджета (number of cells * 74) – 2. updatePeriodMillis — это частота обновления виджета, но не чаще чем раз в 30 минут, в целях экономии батарейки. initialLayout – это файл шаблона виджета.

Widget provider — Это java файл, он должен наследоваться от класса AppWidgetProvider. В нашем случае он пока останется пустым (src/ru/example/android/widget/ HelloWidget.java).

  1. package ru.example.android.widget;
  2. import android.appwidget.AppWidgetProvider;
  3.  
  4. public class HelloWidget extends AppWidgetProvider {
  5. }

Layout – Это шаблон виджета или слой View, кому как нравится. Выглядеть он будет так: (res/layout /main.xml).

  1. <?xml version=»1.0″ encoding=»utf-8″?>
  2. <LinearLayout xmlns:android=»schemas.android.com/apk/res/android»
  3.         android:layout_width=»fill_parent»
  4.         android:orientation=»vertical»
  5.         android:background=»@android:color/white»
  6.         android:layout_gravity=»center»
  7.         android:layout_height=»wrap_content»>
  8.  
  9. <TextView android:id=»@+id/widget_textview»
  10.                 android:text=»Hello Widget»
  11.                 android:layout_height=»wrap_content»
  12.                 android:layout_width=»wrap_content»
  13.                 android:layout_gravity=»center_horizontal|center»
  14.                 android:textColor=»@android:color/black»/>
  15. </LinearLayout>

Всё основное мы сделали, осталось зарегистрировать виджет в AndroidManifest.xml. Для этого добавим в него следующий код в раздел <application>…</application>:

  1. <receiver android:name=».widget.HelloWidget» android:label=»@string/app_name»>
  2.         <intent-filter>
  3.                 <action android:name=»android.appwidget.action.APPWIDGET_UPDATE» />
  4.         </intent-filter>
  5.         <meta-data android:name=»android.appwidget.provider»
  6.                 android:resource=»@xml/hello_widget_provider» />
  7. </receiver>

Теперь можем компилировать проект и смотреть результат в эмуляторе!

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

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

  1. <?xml version=»1.0″ encoding=»utf-8″?>
  2. <LinearLayout xmlns:android=»schemas.android.com/apk/res/android»
  3.         android:layout_width=»fill_parent»
  4.         android:orientation=»vertical»
  5.         android:background=»@android:color/white»
  6.         android:layout_gravity=»center»
  7.         android:layout_height=»wrap_content»>
  8.  
  9.         <TextView android:id=»@+id/widget_textview»
  10.                 android:text=»Hello Widget»
  11.                 android:layout_height=»wrap_content»
  12.                 android:layout_width=»wrap_content»
  13.                 android:layout_gravity=»center_horizontal|center»
  14.                 android:textColor=»@android:color/black»/>
  15.         <Button android:id=»@+id/widget_button»
  16.                 android:text=»click me»
  17.                 android:layout_height=»wrap_content»
  18.                 android:layout_width=»wrap_content»/>
  19.  
  20. </LinearLayout>

Все взаимодействия с виджетом будем делать в классе provider (src/ru/example/android/widget/ HelloWidget.java). Вот как будет выглядеть простейшая обработка события:

  1. public class HelloWidget extends AppWidgetProvider {
  2.  
  3.         public static String ACTION_WIDGET_RECEIVER = «ActionReceiverWidget»;
  4.  
  5.         @Override
  6.         public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
  7.              //Создаем новый RemoteViews
  8.              RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
  9.  
  10.              //Подготавливаем Intent для Broadcast
  11.              Intent active = new Intent(context, HelloWidget.class);
  12.              active.setAction(ACTION_WIDGET_RECEIVER);
  13.              active.putExtra(«msg», «Hello Habrahabr»);
  14.  
  15.              //создаем наше событие
  16.              PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, active, 0);
  17.  
  18.              //регистрируем наше событие
  19.              remoteViews.setOnClickPendingIntent(R.id.widget_button, actionPendingIntent);
  20.  
  21.              //обновляем виджет
  22.              appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
  23.         }
  24.  
  25.         @Override
  26.         public void onReceive(Context context, Intent intent) {
  27.  
  28.              //Ловим наш Broadcast, проверяем и выводим сообщение
  29.              final String action = intent.getAction();
  30.              if (ACTION_WIDGET_RECEIVER.equals(action)) {
  31.                   String msg = «null»;
  32.                   try {
  33.                         msg = intent.getStringExtra(«msg»);
  34.                   } catch (NullPointerException e) {
  35.                         Log.e(«Error», «msg = null»);
  36.                   }
  37.                   Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
  38.              } 
  39.              super.onReceive(context, intent);
  40.        }
  41.  
  42. }

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

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

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

Добовляем изменения в файл AndroidManifest.xml:

  1. <receiver android:name=».widget.HelloWidget» android:label=»@string/app_name»>
  2.        <intent-filter>
  3.              <action android:name=»android.appwidget.action.APPWIDGET_UPDATE» />
  4.              <action android:name=»ru.example.android.widget.ACTION_WIDGET_RECEIVER» />
  5.        </intent-filter>
  6.        <meta-data android:name=»android.appwidget.provider»
  7.                                    android:resource=»@xml/hello_widget_provider» />
  8. </receiver>

Компилируем, и наслаждаемся резульатом.

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

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

Как сделать виджет в смартфоне либо планшете под управлением Android.

Как сделать виджет в смартфоне либо планшете под управлением Android.

%d0%b2%d0%b8%d0%b4%d0%b6%d0%b0%d1%82%d0%b0%d0%bd%d0%b0%d0%bc%d0%b0-%d0%b0%d0%bd%d0%b4%d1%80%d0%be%d0%b8%d0%b4%d0%b0%d0%bd%d0%b0%d0%bc%d0%b0

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

Minimalistic Text

Приложение позволяет создавать минималистичные виджеты, состоящие из текста и чисел. Несмотря на название, у таких виджетов есть весьма широкие возможности.

После установки добавьте на рабочий стол виджет Minimalistic Text требуемого размера. Откроется окно настройки виджета.

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

  • Время.
  • Дата.
  • Уровень заряда.
  • Температура.

Дополнительные возможности по наполнению виджета доступны при создании своего собственного макета виджета:

  • Текст.
  • Свободное/занятое место на карте SD.
  • Время работы системы.
  • Количество переданных данных по мобильной сети и не только.
  • Название сети Wi-Fi, IP адрес в сети Wi-Fi.
  • Нагрузка на CPU.
  • Количество занятой оперативной памяти.

Все величины можно выводить в тексте и в цифрах.

Ultimate custom widget (UCCW)

Данное приложение позволяет делать виджеты не только из текста, но и с графическим содержанием.

При запуске открывается окно создания виджета. Чтобы добавить на виджет объект нажмите кнопку +/- вверху. Чтобы всяким образом перемещать и редактировать его нажмите вверху на кнопку «Select Object» и выберите добавленный объект.

На виджетах можно размещать такие объекты:

  • Время, дату и день недели.
  • Аналоговые часы.
  • Пользовательский текст.
  • Уровень заряда в цифрах или же графическое отображение оного.
  • Всю информацию о погоде — температуру, влажность, скорость ветра и т.д.
  • Геометрические фигуры.
  • Картинки.
  • Количество непрочитанных писем Gmail, sms и пропущенных звонков.
  • Время, когда зазвонит будильник.

Каждый элемент можно вращать, растягивать, сгибать и менять ему цвет. Разумеется, можно настроить действие по клику (Select Object->Hotspots). Поддерживается импорт и экспорт созданных виджетов.

Создаем AppWidget или же виджеты для рабочего стола Android

30 октября, 2012

Автор: Maksim

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

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

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

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

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

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

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

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

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

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

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

      package=»com.example.widget»

      android:versionCode=»1″

      android:versionName=»1.0″>

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

        <receiver android:name=»widget» >

                            <intent-filter>

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

                            </intent-filter>

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

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

            </receiver>

    </application>

</manifest> 

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

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

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

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

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

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

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

            package com.example.widget; 

import android.appwidget.AppWidgetProvider; 

public class widget extends AppWidgetProvider{ 

} 

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

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

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

    android:minWidth=»294dp»

    android:minHeight=»72dp»

    android:updatePeriodMillis=»0″

    android:initialLayout=»@layout/widget»>

</appwidget-provider> 

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

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

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

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

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

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

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

LinearLayout

FrameLayout

RelativeLayout

AnalogClock

Button

Chronometer

ImageButton

ImageView

ProgressBar

TextView

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

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

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

            android:id=»@+id/Widget»

    android:layout_width=»fill_parent»

    android:layout_height=»fill_parent»

    android:orientation=»horizontal»

    android:gravity=»center_vertical»

    android:background=»@drawable/frame»> 

     <AnalogClock

        android:layout_width=»fill_parent»

        android:layout_height=»wrap_content»/> 

</LinearLayout> 

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

новейших вопросов об android-виджетах — qaru

Переполнение стека

  1. Около
  2. Товары

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. работы
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

,

android — программно обновить виджет из активности / службы / приемника

Переполнение стека

  1. Около
  2. Товары

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. работы
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

,

Добавить комментарий

Ваш адрес email не будет опубликован.