Часто встречающаяся задача — создание виджета со счетчиком непрочитанных сообщений/звонков и т.п. Однако в Android нет стандартного класса для создания таких виджетов.

Как же все-таки создать такой виджет?
О том как создавать виджеты и как они работают можно найти в этой и этой статьях, потому не будем останавливаться на деталях создания виджетов как таковых.
Основная задача, которую нам необходимо выполнить — создать виджет, максимально похожий на стандартные иконки и виджеты стандартных приложений.
Попытки найти уже готовое решение ни к чему не привели. Потому пришлось взять в руки APK Manager (спасибо статье про реверс-инжиниринг) и приложение Google Reader for Android — надеялся, что их виджет выглядит стандартно. Оказалось, что это не так:

Немного похоже, конечно, но не совсем то, что нужно. Пришлось доделывать:
Высота виджета — 82dip — больше, чем рекомендуемая. Но иначе наш виджет будет меньше стандартной иконки.
Указанный в коде выше @drawable/shortcut_selector — xml-файл, описывающий, как виджет будет реагировать на нажатия/выделения:
Тут мы сталкиваемся с первой проблемой — pressed_application_background и focused_application_background — изображения фона. Но в разных телефонах фоны разные. Например, в стандартном Android — фон оранжевый, в HTC — зеленый. Решением была бы возможность обратиться к стандартным ресурсам Android'a — благодаря сайту Android R Drawables, можно даже найти id этих ресурсов:
Но эти ресурсы не public и использовать их в своем приложении нельзя. Можно попробовать считывать значение android.os.Build и определять тип OS. Но я решил, что для виджета это не так важно, потому оставил оранжевый фон (кстати, в виджете Google Reader сделано также).
Вторая проблема — аналогична первой. Фон круга с числом непрочитанных сообщений на стандартном Android — красный, на HTC — зеленый. Но тут можно обратиться к стандартному ресурсу — @android:drawable/ic_notification_overlay. Это не круг, но выглядит очень похоже (изображение будет чуть ниже).
Подсветка фона текста взята из виджета Google Reader без изменений (файл appwidget_text_background.xml):
В моем приложении (думаю, как и в других приложениях с таким счетчиком) нет необходимости периодически обновлять значение счетчика. Потому updatePeriodMillis=«0» и его обновление происходит лишь в двух случаях:
При этом для обновления виджета используется следующий код:
Если число непрочитанных сообщений равно нулю, то круг с числом скрывается:
Вот что получилось в итоге:

Выглядит неидеально, но по-моему очень похоже. В эмуляторе выглядит тоже очень похоже на оригинал.

Как же все-таки создать такой виджет?
О том как создавать виджеты и как они работают можно найти в этой и этой статьях, потому не будем останавливаться на деталях создания виджетов как таковых.
Основная задача, которую нам необходимо выполнить — создать виджет, максимально похожий на стандартные иконки и виджеты стандартных приложений.
Попытки найти уже готовое решение ни к чему не привели. Потому пришлось взять в руки APK Manager (спасибо статье про реверс-инжиниринг) и приложение Google Reader for Android — надеялся, что их виджет выглядит стандартно. Оказалось, что это не так:

Немного похоже, конечно, но не совсем то, что нужно. Пришлось доделывать:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:orientation="vertical" android:id="@+id/widget"
android:background="@drawable/shortcut_selector" android:focusable="true"
android:clickable="true" android:layout_width="74.0dip"
android:layout_height="82.0dip"
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout android:layout_width="fill_parent"
android:layout_height="0.0dip" android:layout_weight="1.0"
android:paddingTop="3dip">
<FrameLayout android:layout_gravity="center"
android:layout_width="wrap_content" android:layout_height="wrap_content">
<ImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon"
android:scaleType="center" />
<TextView android:textSize="14.0dip" android:textStyle="bold"
android:textColor="#ffffffff" android:gravity="center"
android:layout_gravity="bottom|right|center"
android:id="@+id/widget_counter"
android:background="@android:drawable/ic_notification_overlay"
android:layout_width="24dip"
android:layout_height="24dip"
android:singleLine="true"
android:shadowColor="#ff000000" android:shadowRadius="1.0"/>
</FrameLayout>
</FrameLayout>
<TextView android:textSize="13.0dip" android:textColor="#ffffffff"
android:ellipsize="marquee" android:gravity="center_horizontal"
android:layout_gravity="center_horizontal" android:id="@android:id/text1"
android:background="@drawable/appwidget_text_background"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="1dip"
android:paddingBottom="1dip"
android:singleLine="true" android:shadowColor="#ff000000"
android:shadowRadius="2.0"
android:layout_weight="0.0"
android:text="@string/app_name" />
</LinearLayout>
Высота виджета — 82dip — больше, чем рекомендуемая. Но иначе наш виджет будет меньше стандартной иконки.
Указанный в коде выше @drawable/shortcut_selector — xml-файл, описывающий, как виджет будет реагировать на нажатия/выделения:
<?xml version="1.0" encoding="UTF-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/pressed_application_background" />
<item android:state_focused="true"
android:state_window_focused="true"
android:drawable="@drawable/focused_application_background" />
<item android:state_focused="true"
android:state_window_focused="false"
android:drawable="@android:color/transparent" />
</selector>
Тут мы сталкиваемся с первой проблемой — pressed_application_background и focused_application_background — изображения фона. Но в разных телефонах фоны разные. Например, в стандартном Android — фон оранжевый, в HTC — зеленый. Решением была бы возможность обратиться к стандартным ресурсам Android'a — благодаря сайту Android R Drawables, можно даже найти id этих ресурсов:
- pressed_application_background_static
- focused_application_background_static
Но эти ресурсы не public и использовать их в своем приложении нельзя. Можно попробовать считывать значение android.os.Build и определять тип OS. Но я решил, что для виджета это не так важно, потому оставил оранжевый фон (кстати, в виджете Google Reader сделано также).
Вторая проблема — аналогична первой. Фон круга с числом непрочитанных сообщений на стандартном Android — красный, на HTC — зеленый. Но тут можно обратиться к стандартному ресурсу — @android:drawable/ic_notification_overlay. Это не круг, но выглядит очень похоже (изображение будет чуть ниже).
Подсветка фона текста взята из виджета Google Reader без изменений (файл appwidget_text_background.xml):
<?xml version="1.0" encoding="UTF-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#b2191919" />
<padding android:left="5.0dip" android:top="1.0dip"
android:right="5.0dip" android:bottom="1.0dip" />
<corners android:radius="8.0dip" />
</shape>
В моем приложении (думаю, как и в других приложениях с таким счетчиком) нет необходимости периодически обновлять значение счетчика. Потому updatePeriodMillis=«0» и его обновление происходит лишь в двух случаях:
- когда происходит обновление в базе данных;
- когда приложение становится невидимым (onStop()).
При этом для обновления виджета используется следующий код:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
int[] a = appWidgetManager.getAppWidgetIds(new ComponentName(this.getPackageName(), "WidgetProvider"));
List<AppWidgetProviderInfo> b = appWidgetManager.getInstalledProviders();
for (AppWidgetProviderInfo i : b) {
if (i.provider.getPackageName().equals(this.getPackageName())) {
a = appWidgetManager.getAppWidgetIds(i.provider);
new WidgetProvider().onUpdate(this, appWidgetManager, a);
}
}
Если число непрочитанных сообщений равно нулю, то круг с числом скрывается:
if (unreadRecordsCount == 0) {
views.setViewVisibility(R.id.widget_counter, View.INVISIBLE);
views.setTextViewText(R.id.widget_counter, "");
} else {
views.setTextViewText(R.id.widget_counter, Long.toString(unreadRecordsCount));
views.setViewVisibility(R.id.widget_counter, View.VISIBLE);
}
Вот что получилось в итоге:

Выглядит неидеально, но по-моему очень похоже. В эмуляторе выглядит тоже очень похоже на оригинал.