Статьи

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

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

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

Это часть 4 из 4 в серии, посвященной созданию настраиваемого виджета аналоговых часов Android в четырех уроках:

Встраивание конфигурации пользователя в наше приложение-виджет будет включать новый класс Java Activity, предоставляющий пользователю выбор вариантов. Когда пользователь выбирает дизайн, мы обновляем внешний вид виджета и сохраняем выбор пользователя в общих настройках приложения. Мы также будем расширять класс виджета для обработки пользовательских кликов на виджете и для чтения из общих настроек по выбору пользователя. Помимо работы с этими двумя файлами Java, мы создадим новый файл значений и файл макета XML для выбора Activity вместе с некоторыми изображениями, которые будут отображаться в нем.


Во-первых, давайте добавим некоторый код в класс виджетов для определения кликов пользователей. В классе «ClockWidget» внутри оператора «if» в методе «onReceive» после строки, в которой мы получили объект Remote Views, добавьте следующий код, чтобы создать Intent для действия выбора, которое мы будем использовать:

1
Intent choiceIntent = new Intent(context, ClockChoice.class);

Не беспокойтесь об ошибках Eclipse, они исчезнут, когда мы создадим новый класс Activity на следующем шаге. После этой строки создайте отложенное намерение следующим образом:

1
2
PendingIntent clickPendIntent = PendingIntent.getActivity
    (context, 0, choiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);

Как вы видите, запуск действий при щелчках виджетов немного отличается. Обратите внимание, что мы передаем объект Context и ссылку на новое намерение. Теперь добавьте следующий код, указывающий, что Pending Intent должен запускаться при нажатии виджета:

1
views.setOnClickPendingIntent(R.id.custom_clock_widget, clickPendIntent);

Мы указываем виджет, ссылаясь на идентификатор родительского макета в XML-файле clock_widget_layout. Нам нужно использовать Remote Views для ссылки на элементы пользовательского интерфейса, поскольку мы находимся в классе виджетов, а не в классе Activity. Мы добавим больше кода в этот класс позже.


Теперь о деятельности, в которой мы позволяем пользователям выбирать дизайн. Создайте новый класс в своем проекте, щелкнув правой кнопкой мыши или выбрав папку исходного пакета и выбрав «Файл», затем выберите «Новый», «Класс» и введите «ClockChoice» в качестве имени класса. Eclipse откроет новый класс, когда вы нажмете Finish. Помните, что мы включили это действие в файл манифеста проекта в части 1.

Сделайте ваш новый класс Activity и тот, который будет обрабатывать клики пользователей, расширяя его начальную строку следующим образом:

1
public class ClockChoice extends Activity implements OnClickListener {

Опять же, просто игнорируйте любые сообщения об ошибках, они будут появляться, пока мы не предоставим метод «onClick». Вам понадобятся следующие операторы импорта:

1
2
import android.app.Activity;
import android.view.View.OnClickListener;

Предоставьте метод Activity «onCreate» внутри класса следующим образом:

1
2
3
4
5
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.clock_choice);
 
}

Мы создадим файл макета на следующем шаге. Вам понадобится еще один импорт:

1
import android.os.Bundle;

Мы будем добавлять больше кода в этот класс позже.


Давайте создадим макет, который мы указали в классе Activity выше. Создайте новый файл макета, щелкнув правой кнопкой мыши или выбрав папку «res / layout» и выбрав «Файл», затем нажмите «Новый», «Файл» и введите «clock_choice.xml», чтобы соответствовать ссылке в приведенном выше коде.

Когда Eclipse откроет файл, выберите вкладку «clock_choice.xml» для редактирования кода. Введите следующую схему расположения в вашем файле:

01
02
03
04
05
06
07
08
09
10
11
<ScrollView xmlns:android=»http://schemas.android.com/apk/res/android»
    android:layout_height=»wrap_content»
    android:layout_width=»fill_parent»>
    <LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
        android:orientation=»vertical»
        android:layout_width=»wrap_content»
        android:layout_height=»wrap_content»
        android:padding=»10dp»>
     
</LinearLayout>
</ScrollView>

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

1
2
3
4
5
<TextView
    android:layout_width=»wrap_content»
    android:layout_height=»wrap_content»
    android:text=»@string/choice_intro»
    android:textStyle=»italic» />

Добавьте указанный здесь ресурс String в ваш файл «strings.xml» — убедитесь, что вы включили его в обе копии файла строк, в «values» и «values-v14»:

1
<string name=»choice_intro»>Choose an option:</string>

Теперь мы собираемся отобразить изображение для представления каждого дизайна часов. Эти изображения будут действовать как кнопки, которые пользователи будут нажимать при выборе дизайна. Создайте свои изображения сейчас, не забывая включать разные версии для каждой плотности. Если вы еще не хотите создавать свои собственные, вы можете использовать изображения в ссылке на скачивание в нижней части учебника — вот версии для каждой средней плотности:

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

Теперь вернитесь к XML-файлу макета clock_choice. Добавьте кнопку «Изображение» для каждого используемого вами дизайна, а также три приведенных ниже примера дизайна, после представления текста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<ImageButton
    android:id=»@+id/design_0″
    android:layout_width=»wrap_content»
    android:layout_height=»wrap_content»
    android:src=»@drawable/clock_image»
    android:contentDescription=»@string/design1″
    android:background=»#00000000″
    android:padding=»5dp»/>
<ImageButton
    android:id=»@+id/design_1″
    android:layout_width=»wrap_content»
    android:layout_height=»wrap_content»
    android:src=»@drawable/clock_image_stone»
    android:contentDescription=»@string/design2″
    android:background=»#00000000″
    android:padding=»5dp»/>
<ImageButton
    android:id=»@+id/design_2″
    android:layout_width=»wrap_content»
    android:layout_height=»wrap_content»
    android:src=»@drawable/clock_image_metal»
    android:contentDescription=»@string/design3″
    android:background=»#00000000″
    android:padding=»5dp» />

Здесь следует отметить несколько моментов. Во-первых, обратите внимание, что у каждой кнопки изображения есть идентификатор с тем же синтаксисом, но с увеличивающимся целым числом в конце — мы будем использовать это для итерации по проектам в Java, поэтому, если вы включаете более трех проектов, убедитесь, что вы назначаете их итерацию числа, такие как «design_3» и т. д. Помимо атрибутов дизайна, каждый элемент также указывает соответствующий ресурс для рисования, поэтому измените их, если только что созданные вами файлы изображений имеют разные имена. Наконец, атрибуты описания содержимого относятся к строковым ресурсам, поэтому добавьте их в файлы «strings.xml» в папках «values» и «values-v14» следующим образом:

1
2
3
<string name=»design1″>Default design</string>
<string name=»design2″>Stone design</string>
<string name=»design3″>Metal design</string>

При необходимости измените эти описания, чтобы они соответствовали вашим собственным проектам.

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

1
2
<activity android:name=».ClockChoice» android:theme=»@android:style/Theme.Dialog»>
</activity>

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


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

Само собой разумеется, что вы можете изменить количество возможных дизайнов в вашем приложении виджета часов. Чтобы упростить этот процесс, мы собираемся, чтобы наш Java-код динамически считывал количество дизайнов. Для этой цели мы собираемся использовать числовое значение, чтобы отслеживать количество дизайнов, которые мы используем. Создайте новый файл в каждой из двух ваших папок значений, щелкнув правой кнопкой мыши или выбрав каждую папку по очереди, выбрав «Файл», затем «Новый», «Файл» и введя «numbers.xml» в качестве имени файла.


Выберите вкладку «numbers.xml» и введите следующий код в новый файл:

1
2
3
<resources>
    <integer name=»num_clocks»>3</integer>
</resources>

Измените число, если вы использовали другое количество дизайнов, убедившись, что значение соответствует количеству кнопок изображения, которые вы включили в макет, и количеству элементов аналоговых часов, которые есть в макете вашего виджета. Помните, что вам нужна копия файла «numbers.xml» в обеих папках значений, поэтому скопируйте и вставьте ее, если необходимо.

Если вы изменяете количество дизайнов, которые вы используете в любой момент, вам нужно изменить значение в файле (файлах) «numbers.xml», добавить каждый дизайн в качестве элемента аналоговых часов в файле приложения «clock_widget_layout» и Кнопка Image для каждого дизайна в файле макета Activity.


Давайте рассмотрим взаимодействие с пользователем с помощью выбора дизайна часов. Откройте ваш файл активности «ClockChoice». Внутри класса перед методом onCreate добавьте следующие переменные экземпляра:

1
2
3
4
5
6
//count of designs
private int numDesigns;
//image buttons for each design
private ImageButton[] designBtns;
//identifiers for each clock element
private int[] designs;

Мы будем использовать эти переменные для перебора различных конструкций. Добавить еще один импорт:

1
import android.widget.ImageButton;

Внутри метода «onCreate», после существующего кода, создайте экземпляр переменной для отслеживания количества дизайнов часов следующим образом:

1
numDesigns = this.getResources().getInteger(R.integer.num_clocks);

Здесь мы извлекаем значение из XML-файла с числами, который мы создали ранее — мы сможем использовать это значение в качестве ссылки при итерации по проектам. Далее создайте экземпляры массивов для кнопок дизайна и элементов часов:

1
2
designBtns = new ImageButton[numDesigns];
designs = new int[numDesigns];

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

Теперь добавьте цикл для итерации по проектам:

1
2
3
for(int d=0; d<numDesigns; d++){
 
}

Внутри цикла извлеките ссылку на каждый элемент Analog Clock в макете виджета:

1
2
designs[d] = this.getResources().getIdentifier
    («AnalogClock»+d, «id», getPackageName());

Если вы посмотрите на файл «clock_widget_layout», вы увидите, что каждый элемент Analog Clock имеет идентификатор, содержащий «AnalogClock», за которым следует увеличивающееся целое число — мы используем это здесь, чтобы получить ссылку на каждый из них, используя счетчик цикла. Затем, по-прежнему внутри цикла, извлеките значения идентификаторов для кнопок изображения в макете «Выбор действий»:

1
2
designBtns[d]=(ImageButton)findViewById(this.getResources().getIdentifier
    («design_»+d, «id», getPackageName()));

Здесь мы используем «findViewById», чтобы получить ссылку на соответствующий элемент Image Button, передавая идентификатор, который содержит «design_», за которым следует увеличивающееся целое число. Теперь установите прослушиватель щелчков для каждой из этих кнопок, чтобы мы могли обрабатывать щелчки в этом классе Activity. Пока еще внутри цикла:

1
designBtns[d].setOnClickListener(this);

Теперь мы можем продолжить с методом щелчка для кнопок дизайна. После метода «onCreate» в классе «ClockChoice» добавьте метод «onClick» следующим образом:

1
2
3
public void onClick(View v) {
 
}

Добавьте следующий импорт:

1
import android.view.View;

В методе «onClick» нам сначала нужно определить, какую кнопку нажал пользователь. Для этого добавьте в метод следующий цикл:

1
2
3
4
5
6
7
int picked = -1;
for(int c=0; c<numDesigns; c++){
    if(v.getId()==designBtns[c].getId()){
        picked=c;
        break;
    }
}

Здесь мы проверяем клик по идентификатору вида по идентификаторам, которые мы сохранили в массиве Image Button. Когда цикл завершается, выбранный индекс сохраняется в локальной переменной. После цикла сохраните ссылку на идентификатор для выбранного элемента Analog Clock:

1
int pickedClock = designs[picked];

Теперь нам нужно получить ссылку на элементы макета виджета, для чего нам нужны Remote Views, передавая макет виджета в качестве ссылки:

1
2
3
RemoteViews remoteViews = new RemoteViews
    (this.getApplicationContext().getPackageName(),
    R.layout.clock_widget_layout);

Для этого вам нужен еще один оператор импорта:

1
import android.widget.RemoteViews;

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

1
2
3
4
for(int d=0; d<designs.length; d++){
    if(d!=pickedClock)
        remoteViews.setViewVisibility(designs[d], View.INVISIBLE);
}

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

1
remoteViews.setViewVisibility(pickedClock, View.VISIBLE);

Теперь, поскольку это приложение для виджетов, нам нужно обновить внешний вид виджета следующим образом:

1
2
3
4
5
6
7
//get component name for widget class
ComponentName comp = new ComponentName(this, ClockWidget.class);
//get AppWidgetManager
AppWidgetManager appWidgetManager =
    AppWidgetManager.getInstance(this.getApplicationContext());
//update
appWidgetManager.updateAppWidget(comp, remoteViews);

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

1
2
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;

Теперь внешний вид виджета будет обновляться по выбору пользователя.


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

1
private SharedPreferences clockPrefs;

Теперь в конце метода «onCreate», после существующего кода, добавьте следующее, чтобы создать экземпляр переменной Shared Preferences:

1
clockPrefs = getSharedPreferences(«CustomClockPrefs», 0);

Мы будем использовать одно и то же имя настроек каждый раз, когда получаем доступ к данным, касающимся выбора пользователя. Теперь перейдите к концу метода «onClick», после кода, в котором мы обновили виджет. Получите редактор настроек следующим образом:

1
SharedPreferences.Editor custClockEdit = clockPrefs.edit();

Теперь передайте данные о выборе пользователя редактору и передайте его:

1
2
custClockEdit.putInt(«clockdesign», picked);
custClockEdit.commit();

Мы указываем имя для значения данных и идентификатор выбранного дизайна. Наконец, по-прежнему внутри «onClick» добавьте следующее по мере выполнения задания Activity:

1
finish();

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

1
2
3
4
5
6
//preferences
private SharedPreferences custClockPrefs;
//number of possible designs
private int numClocks;
//IDs of Analog Clock elements
int[] clockDesigns;

Вам понадобится следующий импорт:

1
import android.content.SharedPreferences;

В методе «onReceive» перед существующим кодом извлеките количество дизайнов из нашего ценностного ресурса:

1
numClocks = context.getResources().getInteger(R.integer.num_clocks);

Далее создайте экземпляр массива идентификаторов часов следующим образом:

1
clockDesigns = new int[numClocks];

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

1
2
3
4
for(int d=0; d<numClocks; d++){
    clockDesigns[d]=context.getResources().getIdentifier
        («AnalogClock»+d, «id», context.getPackageName());
}

Теперь перейдите к содержанию оператора «if» в методе «onReceive», после строки, в которой мы получили Remote Views, и до строки, в которой мы создали Intent для класса выбора часов, получите Shared Preferences и проверьте для значения данных, которое мы устанавливаем для выбора пользователя:

1
2
custClockPrefs = context.getSharedPreferences(«CustomClockPrefs», 0);
int chosenDesign = custClockPrefs.getInt(«clockdesign», -1);

Имя Shared Preferences и имя значения данных должны совпадать с тем, что вы указали при настройке выбора пользователя в вышеупомянутой операции выбора. Возможно, пользователь еще не выбрал дизайн, поэтому включите оператор «if» для проверки:

1
2
3
if(chosenDesign>=0){
             
}

Внутри оператора «if» сначала выполните цикл по проектам, устанавливая каждый невидимый, если он не выбранный:

1
2
3
4
for(int d=0; d<numClocks; d++){
    if(d!=chosenDesign)
        views.setViewVisibility(clockDesigns[d], View.INVISIBLE);
}

Вам понадобится еще один импорт:

1
import android.view.View;

Теперь установите выбранный дизайн видимым:

1
views.setViewVisibility(clockDesigns[chosenDesign], View.VISIBLE);

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


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


Потратьте некоторое время, чтобы оглянуться назад по различным элементам приложения и убедиться, что вы понимаете, как они работают вместе друг с другом. Процесс разработки любого другого приложения виджета будет включать в себя множество одинаковых шагов. Жизненно важными элементами в приложении виджета являются: элемент XML «appwidget-provider», файл Manifest, класс, расширяющий AppWidgetProvider, и, конечно, визуальные элементы, включая макеты и ресурсы.

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

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