Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Теория
Создание виджета "Отсчёт до праздника"
Виджет для вывода даты и времени
Кроме обычных приложений существуют еще виджеты, которые размещают на домашнем экране устройства. Смысл такого виджета заключаются в том, что мы сразу видим определённую информацию прямо на экране без необходимости запускать приложение. Например, можно вывести информацию о количестве непрочитанных писем, напоминание о встрече, о погоде в городе и т.д.
Android предоставляет необходимый инструментарий для создания такого вида приложений. Виджеты могут оказаться полезными для определённого вида программ, которые должны сообщать об обновлении статуса и информации. Сегодня мы рассмотрим пример создания такого виджета.
С появлением Compose создание виджетов немного изменилось. Некоторая часть кода осталась без изменений, но непосредственная работа с виджетом идёт через новую библиотеку Glance.
Первая версия статьи писалась ещё для Android 2.3. С тем пор произошло много изменений. Сначала была добавлена новая функциональность в Android 3.1, потом появились новинки в Android 4.x. Поэтому нужно всегда сверяться с документацией.
Есть и хорошая новость - в Android Studio появился готовый шаблон для создания виджетов, который значительно облегчил задачу, автоматизировав часть рутинной работы. До этого приходилось всё делать вручную. При описании урока я постараюсь показать, как стало намного лучше.
Виджет может быть самостоятельной единицей. Иными словами, вы можете удалить из проекта все активности, оставив только те файлы, которые отвечают за виджет. Например, это удобно, если виджет служит для быстрого включения или отключения какой-нибудь системной настройки, скажем, Wi-Fi. В этом случае само приложение с экранами активностей нам ни к чему. Но в большинстве случаев, виджет и приложение взаимосвязаны. Например, приложение о футболе выводит в виджете счёт последнего матча любимой команды, приложение о погоде выводит прогноз погоды в вашем городе и т.д.
Создадим стандартный проект CatDays. Активность оставляем в покое, наш виджет не нуждается в ней. А пойдём дальше.
Вызываем через контекстное меню имени пакета New | Widget | App Widget диалоговое окно и создаём новый класс-заготовку.

В первой строчке указываем имя класса. Остальные настройки можно оставить по умолчанию. Я только вкратце поясню их назначение.
Выпадающий список Placement позволяет разместить виджет только на домашнем экране (Home-screen), на экране блокировки (Keyguard) или там и там (Both).
Настройка Resizable позволяет создавать виджеты с изменяемыми размерами. Можно менять размеры в обоих направлениях или только по горизонтали или только по вертикали. А можно сделать виджет неизменяемым.
Настройки Minimum Width (cells) и Minimum Height (cells) определяют минимальные размеры в условных единицах - cell (клетка). Домашний экран устройства условно делится на клетки и пользователь может перемещать имеющиеся у него виджеты по этим клеткам.
Пункт Configuration Screen позволяет создать экран настроек виджета, например, задать цвет фона и тому подобное.
Выбор языка остаётся за вами - Java или Kotlin.
Нажимаем кнопку Finish и студия создаст изменения в проекте.
Во-первых, будет создан новый класс CatDaysAppWidget с некоторым количеством кода.
Во-вторых, в папке res/layout появится новый файл cat_days_app_widget.xml, отвечающий за разметку виджета.
В-третьих, появится папка res/xml с файлом cat_days_app_widget_info.xml.
В-четвёртых, в манифесте появятся новые записи о добавленном виджете.
В-пятых, в папке res/drawable-nodpi появится файл example_appwidget_preview.png, отвечающий за превьюшку виджета.
А раньше приходилось всё это делать вручную. Чувствуете разницу?
Начнём с файла res/xml/cat_days_app_widget_info.xml. Файл описывает метаданные виджета. В нём указываются размеры виджета, частота его обновления, файл шаблона и класс конфигурации. Корневым элементом является appwidget-provider.
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description"
android:initialKeyguardLayout="@layout/cat_days_app_widget"
android:initialLayout="@layout/cat_days_app_widget"
android:minWidth="40dp"
android:minHeight="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:previewLayout="@layout/cat_days_app_widget"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen" />
Наиболее используемые атрибуты для провайдера.
Теперь откроем файл манифеста - добавляется тег receiver с метаданными , который определяет получаемые намерения и другую информацию.
<receiver
android:name=".CatDaysAppWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/cat_days_app_widget_info" />
</receiver>
Разметка для виджета несколько отличается от шаблонов для активностей. Можно использовать следующие элементы:
Использовать классы, расширяющие указанные выше элементы, не допускается. Поэтому возможности несколько ограничены.
Начиная с Android 4.0, виджеты автоматически получают дополнительное место вокруг себя, чтобы соседние виджеты не слипались.
Переходим в класс виджета CatDaysAppWidget.
package ru.alexanderklimov.catdays
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
/**
* Implementation of App Widget functionality.
*/
class CatDaysAppWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val widgetText = context.getString(R.string.appwidget_text)
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.cat_days_app_widget)
views.setTextViewText(R.id.appwidget_text, widgetText)
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
package ru.alexanderklimov.catdays;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
/**
* Implementation of App Widget functionality.
*/
public class CatDaysAppWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
CharSequence widgetText = context.getString(R.string.appwidget_text);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.cat_days_app_widget);
views.setTextViewText(R.id.appwidget_text, widgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
Класс наследуется не от Activity, а от AppWidgetProvider. В отличие от стандартных активностей, в виджетах вместо метода onCreate() используется метод onUpdate().
Класс AppWidgetProvider содержит несколько методов, которые можно переопределить
Отображение виджета происходит через интерфейс RemoteViews. Объект типа RemoteViews используется в тех случаях, когда отображение представления (виджета) будет происходить из другого процесса. Виджеты отображаются из хост-процесса виджетов, а не из основного процесса приложения.
Ради интереса запустите проект без внесения каких-либо изменений. Закройте активность вашего приложения. Затем запустите программу Приложения и перейдите на вкладку Widgets. Найдите свой виджет с текстом Example. Перетащите виджет на домашний экран. По умолчанию виджет занимает одну клетку и выглядит не слишком красиво. Нажмите на виджет, чтобы появились маркеры изменения размеров и увеличьте размер на одну клетку по горизонтали.

Общее представление вы уже получили, научившись выводить статичную картинку.
Следует признаться, что с тех пор, как я написал очередную версию статьи для устройства Android 4.0, я больше не возвращался к этой теме. Поэтому дальше идут старые материалы под Java.
Наш виджет будет вычислять количество дней до главного праздника - Всемирного дня кошек, который ежегодно отмечается 1 марта.
Добавим в проект картинку, которая послужит нам фоном для виджета. Пусть это будет изображение лапы кота. Размещаем файл в папке drawable-nodpi.
Добавим новые строчки в res/values/strings.xml. Кстати, попутно обнаружилось, что мастер здесь также внёс своё изменение, седьмое по счёту.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Всемирный День Кошек</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="appwidget_text">EXAMPLE</string>
<string name="add_widget">Add widget</string>
</resources>
Откроем разметку cat_days_app_widget.xml из res/layout и модифицируем его. Следует запомнить, что виджет обладает меньшей функциональностью, чем обычное приложение. Поэтому не все элементы управления можно размещать в макете виджета. Элемент TextView входит в число разрешённых элементов и нам его вполне достаточно для вывода текста.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/widget_margin"
android:background="@drawable/paw">
<TextView
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/appwidget_text"
android:textColor="#f11c8a"
android:textSize="24sp"
android:textStyle="bold|italic"
android:layout_margin="8dp"
android:contentDescription="@string/appwidget_text" />
</RelativeLayout>
Предварительные приготовления закончены, пора приступить к написанию программного кода. Открываем java-файл класса виджета и пишем следующее:
package ru.alexanderklimov.catdays;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* Implementation of App Widget functionality.
*/
public class CatDaysAppWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
Calendar catDay = new GregorianCalendar(2016, 2, 1);
Calendar today = Calendar.getInstance();
long days = ((catDay.getTimeInMillis() - today.getTimeInMillis()) / (24 * 60 * 60 * 1000)) + 1;
//CharSequence widgetText = context.getString(R.string.appwidget_text);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.cat_days_app_widget);
// views.setTextViewText(R.id.appwidget_text, widgetText);
views.setTextViewText(R.id.appwidget_text, "Осталось дней: " + days);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
Я вставил в заготовку немного своего кода. Вычисляем количество дней между сегодняшней датой и 1 марта 2016 года. Обратите внимание, что отсчет месяцев начинается с 0, поэтому март - второй месяц. Так как время вычисляется в миллисекундах, то нужно разделить получившееся число на число миллисекунд, секунд, минут, часов, чтобы получить количество дней. Я также прибавил к результату еще один день, чтобы результат был более привычным.
Запускайте проект и установите виджет.
Если всё сделали правильно, то на домашнем экране устройства у вас появится новый виджет, который будет отображать количество дней до главного кошачьего праздника! Позаботьтесь о подарке для кота заранее!
Как видите, ничего сложного в создании виджетов для экранов нет. По скриншоту видно, что можно было заранее установить размер ячеек 2:1 для вывода информации, выбрать более подходящий фон и поработать с отступами для текста.
Для закрепления материала рассмотрим еще один пример виджета, выводящий текущую дату и время. Мы добавим к виджету возможность конфигурирования и посмотрим на реализацию других методов виджета.
Снова воспользуемся готовым мастером создания виджетов, но поставим флажок у пункта Configuration Screen. Минимальную ширину я установил в значение 3 ячейки.
Разметку оставим простейшую - текстовой метки TextView, предлагаемой по умолчанию, вполне достаточно для нашей задачи.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/widget_margin"
android:background="#09C">
<TextView
android:id="@+id/appwidget_text_datetime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/appwidget_text"
android:textColor="#ffffff"
android:textSize="24sp"
android:textStyle="bold|italic"
android:layout_margin="8dp"
android:contentDescription="@string/appwidget_text"
android:background="#09C" />
</RelativeLayout>
Посмотрим, что у нас в файле res/xml/date_time_app_widget_info.xml:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/example_appwidget_preview"
android:initialLayout="@layout/date_time_app_widget"
android:configure="ru.alexanderklimov.catdays.DateTimeAppWidgetConfigureActivity"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:initialKeyguardLayout="@layout/date_time_app_widget"></appwidget-provider>
Обратите внимание на атрибут android:configure, в котором прописан класс DateTimeAppWidgetConfigureActivity. Откроем созданный за нас файл активности. Вы увидите, что это стандартная активность, содержащая несколько компонентов. Я внёс несколько косметических изменений, а в основом код остался нетронутым.
package ru.alexanderklimov.catdays;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
/**
* The configuration screen for the {@link DateTimeAppWidget DateTimeAppWidget} AppWidget.
*/
public class DateTimeAppWidgetConfigureActivity extends Activity {
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
EditText mAppWidgetText;
Button mAddButton;
private static final String PREFS_NAME = "ru.alexanderklimov.catdays.DateTimeAppWidget";
private static final String PREF_PREFIX_KEY = "appwidget_";
public DateTimeAppWidgetConfigureActivity() {
super();
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if the user presses the back button.
setResult(RESULT_CANCELED);
setContentView(R.layout.date_time_app_widget_configure);
mAppWidgetText = (EditText) findViewById(R.id.appwidget_text);
mAddButton = (Button) findViewById(R.id.add_button);
mAddButton.setOnClickListener(mOnClickListener);
// Find the widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If this activity was started with an intent without an app widget ID, finish with an error.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
return;
}
mAppWidgetText.setText(loadTitlePref(DateTimeAppWidgetConfigureActivity.this, mAppWidgetId));
}
View.OnClickListener mOnClickListener = new View.OnClickListener() {
public void onClick(View v) {
final Context context = DateTimeAppWidgetConfigureActivity.this;
// When the button is clicked, store the string locally
String widgetText = mAppWidgetText.getText().toString();
saveTitlePref(context, mAppWidgetId, widgetText);
// It is the responsibility of the configuration activity to update the app widget
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
DateTimeAppWidget.updateAppWidget(context, appWidgetManager, mAppWidgetId);
Toast.makeText(context, "onClick(): " + String.valueOf(mAppWidgetId),
Toast.LENGTH_LONG).show();
// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
};
// Write the prefix to the SharedPreferences object for this widget
static void saveTitlePref(Context context, int appWidgetId, String text) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putString(PREF_PREFIX_KEY + appWidgetId, text);
prefs.commit();
}
// Read the prefix from the SharedPreferences object for this widget.
// If there is no preference saved, get the default from a resource
static String loadTitlePref(Context context, int appWidgetId) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
String titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);
if (titleValue != null) {
return titleValue;
} else {
return context.getString(R.string.appwidget_text);
}
}
static void deleteTitlePref(Context context, int appWidgetId) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.remove(PREF_PREFIX_KEY + appWidgetId);
prefs.commit();
}
}
В манифесте ничего не трогаем, там всё сделано за нас. Добавленная активность выглядит следующим образом:
<activity android:name=".DateTimeAppWidgetConfigureActivity" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
Откроем макет для экрана конфигурации.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/configure"
android:layout_marginBottom="8dp" />
<EditText
android:id="@+id/appwidget_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<Button
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_widget"
android:layout_marginTop="8dp" />
</LinearLayout>
Теперь осталось написать код для виджета.
package ru.alexanderklimov.catdays;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Implementation of App Widget functionality.
* App Widget Configuration implemented in {@link DateTimeAppWidgetConfigureActivity DateTimeAppWidgetConfigureActivity}
*/
public class DateTimeAppWidget extends AppWidgetProvider {
private static SimpleDateFormat formatter = new SimpleDateFormat(
"dd MMM yyyy hh:mm:ss a");
static String sWidgetText = "";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
final int N = appWidgetIds.length;
// for (int i = 0; i < N; i++) {
// updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
// }
for (int i = 0; i < N; i++) {
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
Toast.makeText(
context,
"onUpdate(): " + String.valueOf(i) + " : "
+ String.valueOf(appWidgetId), Toast.LENGTH_LONG)
.show();
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// When the user deletes the widget, delete the preference associated with it.
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
DateTimeAppWidgetConfigureActivity.deleteTitlePref(context, appWidgetIds[i]);
}
Toast.makeText(context, "onDeleted()", Toast.LENGTH_LONG).show();
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
Toast.makeText(context, "onEnabled()", Toast.LENGTH_LONG).show();
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
Toast.makeText(context, "onDisabled()", Toast.LENGTH_LONG).show();
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
String currentTime = formatter.format(new Date());
sWidgetText = currentTime;
//CharSequence widgetText = DateTimeAppWidgetConfigureActivity.loadTitlePref(context, appWidgetId);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.date_time_app_widget);
views.setTextViewText(R.id.appwidget_text_datetime,
"[" + String.valueOf(appWidgetId) + "]" + sWidgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
Toast.makeText(
context,
"updateAppWidget(): " + String.valueOf(appWidgetId) + "\\n"
+ sWidgetText, Toast.LENGTH_LONG).show();
}
}
Находим свой виджет и пытаемся его установить. Появится экран конфигурационной активности. Нажимаем на кнопку ADD WIDGET и виджет с текущей датой будет установлен на домашнем экране.

Особенность данного виджета состоит в том, что пользователь может установить несколько копий виджетов на экран. В квадратных скобках выводится идентификатор виджета, а затем текущие даты и время. Обратите внимание на всплывающие сообщения, которые будут появляться при срабатывании методов onDeleted(), onDisabled(), onEnabled(), onUpdate().
Если оставить шаблон кода в классе конфигурационной активности и в классе виджета без изменений, то вы бы увидели пример, когда при конфигурации можно ввести любое слово, которое сохраняется в настройках приложения через SharedPreferences. Закомментируем добавленный код, чтобы увидеть пример в действии
package ru.alexanderklimov.catdays;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Implementation of App Widget functionality.
* App Widget Configuration implemented in {@link DateTimeAppWidgetConfigureActivity DateTimeAppWidgetConfigureActivity}
*/
public class DateTimeAppWidget extends AppWidgetProvider {
// private static SimpleDateFormat formatter = new SimpleDateFormat(
// "dd MMM yyyy hh:mm:ss a");
// static String sWidgetText = "";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
}
// for (int i = 0; i < N; i++) {
// int appWidgetId = appWidgetIds[i];
// updateAppWidget(context, appWidgetManager, appWidgetIds[i]);
//
// Toast.makeText(
// context,
// "onUpdate(): " + String.valueOf(i) + " : "
// + String.valueOf(appWidgetId), Toast.LENGTH_LONG)
// .show();
// }
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// When the user deletes the widget, delete the preference associated with it.
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
DateTimeAppWidgetConfigureActivity.deleteTitlePref(context, appWidgetIds[i]);
}
// Toast.makeText(context, "onDeleted()", Toast.LENGTH_LONG).show();
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
// Toast.makeText(context, "onEnabled()", Toast.LENGTH_LONG).show();
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
// Toast.makeText(context, "onDisabled()", Toast.LENGTH_LONG).show();
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
// String currentTime = formatter.format(new Date());
// sWidgetText = currentTime;
CharSequence widgetText = DateTimeAppWidgetConfigureActivity.loadTitlePref(context, appWidgetId);
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.date_time_app_widget);
views.setTextViewText(R.id.appwidget_text_datetime,
widgetText);
// views.setTextViewText(R.id.appwidget_text_datetime,
// "[" + String.valueOf(appWidgetId) + "]" + sWidgetText);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
// Toast.makeText(
// context,
// "updateAppWidget(): " + String.valueOf(appWidgetId) + "\n"
// + sWidgetText, Toast.LENGTH_LONG).show();
}
}
Экран конфигурации. Вводим любимые слова.
На домашнем экране видим виджет с текстом.
Как уже говорил в начале статьи, начиная с Android 4.2, у разработчик появилась возможность размещать виджет не только на домашнем экране, но и на экране блокировке. Для этого следует прописать в манифесте дополнительные параметры и задать различные свойства. Подробнее в документации.
На Хабре есть статья Widgets. Custom fonts / Хабрахабр о подключении своего шрифта к виджету. С небольшими сокращениями приведу здесь на память.
Система не позволяет напрямую устанавливать свой шрифт для виджетов. Для решения проблемы можно использовать картинки.
Первое - найти подходящий шрифт и разместить в папке assets/fonts/.
Далее подключить его в коде виджета:
Typeface tf = Typeface.createFromAsset(context.getAssets(),"fonts/Benegraphic.ttf");
После этого нужно сконвертировать текст в Bitmap с помощью метода.
private Bitmap convertToImg(String text, Context context) {
Bitmap btmText = Bitmap.createBitmap(400, 100, Bitmap.Config.ARGB_4444);
Canvas cnvText = new Canvas(btmText);
Typeface tf = Typeface.createFromAsset(context.getAssets(),"fonts/Benegraphic.ttf");
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setSubpixelText(true);
paint.setTypeface(tf);
paint.setColor(Color.WHITE);
paint.setTextSize(50);
cnvText.drawText(text, 150, 50, paint);
return btmText;
}
Метод convertToImg() возвращает уже готовый «битмап» с текстом, написанный шрифтом Benegraphic.ttf. Нам же остается только установить его в виджете.
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
remoteViews.setImageViewBitmap(R.id.imText, convertToImg("Hello World!", context));
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
}
Виджет управления режимом звука