Pull to refresh

Виджет со счетчиком непрочитанных сообщений

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

image

Как же все-таки создать такой виджет?


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

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

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

image

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

<?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 этих ресурсов:
  1. pressed_application_background_static
  2. 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» и его обновление происходит лишь в двух случаях:
  1. когда происходит обновление в базе данных;
  2. когда приложение становится невидимым (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);
  }


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

image

Выглядит неидеально, но по-моему очень похоже. В эмуляторе выглядит тоже очень похоже на оригинал.
Tags:
Hubs:
Total votes 81: ↑74 and ↓7+67
Comments22

Articles