Сегодня мы будем создавать виджет, который может обновлять текст в нём по нажатию на одну из его четырёх кнопок. По нажатию на первые две будет вызываться MainActivity и обновлять текст на тот, что был передан в зависимости от того, какая из двух кнопок нажата. По нажатию на третью и четвёртую текст просто будет обновляться с помощью IntentSerive службы.
Код проекта доступен по ссылке https://github.com/developersu/MyWidgetNoConf/ .
Создадим новый проект с именем MyWidgetNoConf.
Опишем в манифесте все наши классы
Код проекта доступен по ссылке https://github.com/developersu/MyWidgetNoConf/ .
Создадим новый проект с именем MyWidgetNoConf.
Опишем в манифесте все наши классы
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.blogspot.developersu.mywidgetnoconf"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".myWidget"> <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> <service android:name=".MyIntentService" android:exported="false"> </service> </application> </manifest>
Теперь добавим XML с описанием возможнотей нашего виджета.
res→xml→widget_info.xml
res→xml→widget_info.xml
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minHeight="180dp" android:minWidth="180dp" android:updatePeriodMillis="1800000" android:resizeMode="horizontal|vertical" />
Добавим layout-файл для виджета:
res→layout→my_widget_layout.xml
res→layout→my_widget_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/widgetText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="0" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <Button android:id="@+id/widgetBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:text="Button 1" /> <Button android:id="@+id/widgetBtn2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:text="Button 2" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal"> <Button android:id="@+id/widgetBtn3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 3" /> <Button android:id="@+id/widgetBtn4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Button 4" /> </LinearLayout> </LinearLayout>
Получится следующее:
Добавим классы MyIntentService и myWidget в основной package.
java→com.blogspot.developersu.mywidgetnoconf
→MyIntentService
→myWidget
Перейдём собственно к коду. Начнём с описания функционала виджета. Мы будем использовать onUpdate и onEnabled плюс функцию для определения функций клавиш. Сохранением данных тестовом поле пренебрежем.
Итак, для начала код:
java→com.blogspot.developersu.mywidgetnoconf
→MyIntentService
→myWidget
Перейдём собственно к коду. Начнём с описания функционала виджета. Мы будем использовать onUpdate и onEnabled плюс функцию для определения функций клавиш. Сохранением данных тестовом поле пренебрежем.
Итак, для начала код:
package com.blogspot.developersu.mywidgetnoconf; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; public class myWidget extends AppWidgetProvider { private void setWidgetIntents(Context context, AppWidgetManager appWidgetManager, int appWidgetIds[]){ RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.my_widget_layout); for (int appWidgetId : appWidgetIds) { Intent intentBtn = new Intent(context, MainActivity.class); intentBtn.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intentBtn.putExtra("signature", "Button 1"); intentBtn.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP ); PendingIntent pi = PendingIntent.getActivity(context, appWidgetId+0, intentBtn, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.widgetBtn, pi); /*===================================================================*/ // Set the same for button 2 Intent intentBtn2 = new Intent(context, MainActivity.class); intentBtn2.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intentBtn2.putExtra("signature", "Button 2"); intentBtn2.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP ); PendingIntent pi2 = PendingIntent.getActivity(context, appWidgetId+1, intentBtn2, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.widgetBtn2, pi2); /*===================================================================*/ Intent intentBtn3 = new Intent(context, MyIntentService.class); intentBtn3.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intentBtn3.putExtra("signature", "Button 3"); PendingIntent pi3 = PendingIntent.getService(context, appWidgetId+2, intentBtn3, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.widgetBtn3, pi3); /*===================================================================*/ Intent intentBtn4 = new Intent(context, MyIntentService.class); intentBtn4.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intentBtn4.putExtra("signature", "Button 4"); PendingIntent pi4 = PendingIntent.getService(context, appWidgetId+3, intentBtn4, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.widgetBtn4, pi4); appWidgetManager.updateAppWidget(appWidgetId, rv); } } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); setWidgetIntents(context, appWidgetManager, appWidgetIds); } @Override public void onEnabled(Context context) { AppWidgetManager awm = AppWidgetManager.getInstance(context); ComponentName compName = new ComponentName(context, myWidget.class); int[] widgetIds = awm.getAppWidgetIds(compName); // избыточно, но оставим для наглядности setWidgetIntents(context, awm, widgetIds); } }
У нас есть функция setWidgetIntents которая принимает context, AppWidgetManager и множество ID среди которых есть и ID нашего виджета.
onUpdate — все данные у нас сразу имеются, так что просто передаём их в функцию
onEnabled — создаём объект AppWidgetManager используя AppWidgetManager.getInstance(context), создаём ComponentName, который нам понадобится для вытаскивания передавая ему context и имя класса нашего виджета. IDs получаем через awm.getAppWidgetIds(compName).
setWidgetIntents — опеределим “RemoteViews rv” чтобы через него присваивать кнопкам действия. Далее создадим цикл для всех полученных widget ID.
Присваивать каждой кнопке действия следует используя PendingIntent. Для него требуется Intent в который в первом и втором случае обращён к MainActivitiy.class, а в третьем и четвёртом — к MyIntentService.class.
В каждый такой Intent мы помещаем значение кнопки, которое будет передаваться. Это будет строка с именем “signature” и значением “Button N”, где N номер кнопки. Также передаём в него ID виджета, который инициировал вызов Intent.
Кроме того, в первых двух случаях мы определяем флаг Intent.FLAG_ACTIVITY_SINGLE_TOP который говорит системе, что Activity не должен запускаться, если экземпляр уже существует в стеке. Таким образом, если activity уже запущен, то приниматься Intent с этим флагом будет в методе onNewIntent(). Альтернативой этому может быть установка в манифесте launchMode в singleTop (тогда этот флаг не нужен):
onUpdate — все данные у нас сразу имеются, так что просто передаём их в функцию
onEnabled — создаём объект AppWidgetManager используя AppWidgetManager.getInstance(context), создаём ComponentName, который нам понадобится для вытаскивания передавая ему context и имя класса нашего виджета. IDs получаем через awm.getAppWidgetIds(compName).
setWidgetIntents — опеределим “RemoteViews rv” чтобы через него присваивать кнопкам действия. Далее создадим цикл для всех полученных widget ID.
Присваивать каждой кнопке действия следует используя PendingIntent. Для него требуется Intent в который в первом и втором случае обращён к MainActivitiy.class, а в третьем и четвёртом — к MyIntentService.class.
В каждый такой Intent мы помещаем значение кнопки, которое будет передаваться. Это будет строка с именем “signature” и значением “Button N”, где N номер кнопки. Также передаём в него ID виджета, который инициировал вызов Intent.
Кроме того, в первых двух случаях мы определяем флаг Intent.FLAG_ACTIVITY_SINGLE_TOP который говорит системе, что Activity не должен запускаться, если экземпляр уже существует в стеке. Таким образом, если activity уже запущен, то приниматься Intent с этим флагом будет в методе onNewIntent(). Альтернативой этому может быть установка в манифесте launchMode в singleTop (тогда этот флаг не нужен):
<activity android:name=".MainActivity" android:launchMode="singleTop">
Но этот вариант мы опустим. Трогать манифест не будем :)
Для обращения к сервису этот флаг нам не потребуется, т.к. сервисы работают совсем не так как обычные Activity.
В конце мы “создаём” PendingIntent обращаясь к getActivity или getService. Оба этих метода принимают 4 значения: context, requestCode, Intent и флаги. В requestCode мы будем заносить ID нашего виджет плюс какую-то цифру, чтобы определить каждый PendingIntent отдельно. Их стандартное поведение может поначалу ввести в заблуждение, но, если коротко, то для разных PendingIntent лучше бы иметь разные коды. Используемый флаг PendingIntent.FLAG_UPDATE_CURRENT говорит системе, что если описываемый PendingIntent уже существует, то следует лишь заменить его extra-данные (те, что мы добавляли используя метод addExtra).
Далее присваеваем каждой кнопке действие и “применяем измененеия”
Для обращения к сервису этот флаг нам не потребуется, т.к. сервисы работают совсем не так как обычные Activity.
В конце мы “создаём” PendingIntent обращаясь к getActivity или getService. Оба этих метода принимают 4 значения: context, requestCode, Intent и флаги. В requestCode мы будем заносить ID нашего виджет плюс какую-то цифру, чтобы определить каждый PendingIntent отдельно. Их стандартное поведение может поначалу ввести в заблуждение, но, если коротко, то для разных PendingIntent лучше бы иметь разные коды. Используемый флаг PendingIntent.FLAG_UPDATE_CURRENT говорит системе, что если описываемый PendingIntent уже существует, то следует лишь заменить его extra-данные (те, что мы добавляли используя метод addExtra).
Далее присваеваем каждой кнопке действие и “применяем измененеия”
rv.setOnClickPendingIntent(R.id.widgetBtn, pi); appWidgetManager.updateAppWidget(appWidgetId, rv);
Перейдём теперь к классу MyIntentService.
Класс наследует IntentService и реагирует ловит входящие Intent в onHandleIntent. При запуске вызыватся конструктор, порождается новая нить, которая после выполнения метода onHandleIntent() завершается.
Переходим к коду. Ничего, чтобы ещё хотелось выделить отдельно у меня нет.
Класс наследует IntentService и реагирует ловит входящие Intent в onHandleIntent. При запуске вызыватся конструктор, порождается новая нить, которая после выполнения метода onHandleIntent() завершается.
Переходим к коду. Ничего, чтобы ещё хотелось выделить отдельно у меня нет.
package com.blogspot.developersu.mywidgetnoconf; import android.app.IntentService; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.os.Bundle; import android.widget.RemoteViews; public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { if (intent != null) { Bundle bndle = intent.getExtras(); int wID = bndle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); String btnPressed = bndle.getString("signature"); RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_widget_layout); AppWidgetManager awm = AppWidgetManager.getInstance(getApplicationContext()); rv.setTextViewText(R.id.widgetText, btnPressed); awm.updateAppWidget(wID, rv); } } }
Перейдём к MainActivity.
Тут как не было элементов управления так и нет. При получении Intent будет вызываться или onCreate() (если приложение закрыто) или onNewIntent() (если экземляр уже существует). Определением того, что мы получили в прилетевшем Intent будет заниматься метод handleIntent(), который принимает Intent.
В случае, если Intent не пустой и содержит хоть какие-то extras, будем пытаться(!) брать из него предполагаемые данные, т.е. строку “signature” и ID виджета.
Сразу после этого, выведем тост (Toast) с полученой информацией. Если пользователь просто запускает приложение, то ему покажется уведомление с widget ID = 0 и string = null. Далее приложение проверит эти поля и если они отличны от вышеприведённых значений, то присвоит строку в TextView виджета. За присвоение отвечает метод sendBackToWidget().
Тут как не было элементов управления так и нет. При получении Intent будет вызываться или onCreate() (если приложение закрыто) или onNewIntent() (если экземляр уже существует). Определением того, что мы получили в прилетевшем Intent будет заниматься метод handleIntent(), который принимает Intent.
В случае, если Intent не пустой и содержит хоть какие-то extras, будем пытаться(!) брать из него предполагаемые данные, т.е. строку “signature” и ID виджета.
Сразу после этого, выведем тост (Toast) с полученой информацией. Если пользователь просто запускает приложение, то ему покажется уведомление с widget ID = 0 и string = null. Далее приложение проверит эти поля и если они отличны от вышеприведённых значений, то присвоит строку в TextView виджета. За присвоение отвечает метод sendBackToWidget().
package com.blogspot.developersu.mywidgetnoconf; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.RemoteViews; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private void handleIntent(Intent i){ String recievedString; Bundle bndle = i.getExtras(); if (bndle != null) { int awID = bndle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); recievedString = bndle.getString("signature"); Toast.makeText(getApplicationContext(), "Got intent from: " + Integer.toString(awID) + " " + recievedString, Toast.LENGTH_SHORT).show(); if (awID != 0 && recievedString != null) sendBackToWidget(awID, recievedString); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handleIntent(getIntent()); /* Почему мы вызываем handleIntent прямо тут? Потому, что если приложение закрыто а не работает в фоне, то при запуске его из виджета будет вызван * onCreate. Очевидно, что если это приложение уже будет находиться в стеке, то вызываться будет что-то другое :) */ } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); //setIntent(intent); // Удобно использовать, если бы наш Intent был объявлен глобальной переменной. Тогда бы мы просто переопределили его через setintent(). handleIntent(intent); } private void sendBackToWidget(int widgetID, String str){ AppWidgetManager awManager = AppWidgetManager.getInstance(getApplicationContext()); RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_widget_layout); rv.setTextViewText(R.id.widgetText, str); awManager.updateAppWidget(widgetID, rv); } }
Код не идеален и оставляет множество мест для оптимизаций. Впрочем, нет ничего хуже, чем преждевременная оптимизация, так что оставлю это вам.
Посмотрим на то, что получилось.
Посмотрим на то, что получилось.
На этом всё.
Полезные ссылки:
http://startandroid.ru/ru/uroki/vse-uroki-spiskom/204-urok-119-pendingintent-flagi-requestcode-alarmmanager.html
https://webcache.googleusercontent.com/search?q=cache:FZH45X8YQigJ:www.helloandroid.com/tutorials/communicating-between-running-activities+&cd=6&hl=ru&ct=clnk&gl=ru&lr=lang_en%7Clang_ru%7Clang_uk
Полезные ссылки:
http://startandroid.ru/ru/uroki/vse-uroki-spiskom/204-urok-119-pendingintent-flagi-requestcode-alarmmanager.html
https://webcache.googleusercontent.com/search?q=cache:FZH45X8YQigJ:www.helloandroid.com/tutorials/communicating-between-running-activities+&cd=6&hl=ru&ct=clnk&gl=ru&lr=lang_en%7Clang_ru%7Clang_uk
0 коммент.:
Отправить комментарий