Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
CheckBoxPreference
EditTextPreference
ListPreference
SwitchPreference
RingtonePreference
PreferenceCategory
PreferenceScreen
Импорт системных настроек
Настройки с зависимостями
Отслеживание изменений в Общих настройках
Меняем фон экрана настройки
SeekBarDialogPreference
NumberPickerDialogPreference
Секретная настройка VolumePreference
PreferenceFragment
Настройки могут быть скрытыми или открытыми. Например, мы можем подсчитывать, сколько раз пользователь запускал нашу программу и при сотом запуске подарить ему бонус. Счётчик запуска приложения мы храним в файле настроек, но пользователь ничего не знает об этом. Но бывает так, что нам необходимо дать пользователю настроить функциональность приложения под свои вкусы. Например, сделать настройку "Напоминать о дне рождения котика". Если пользователь установит флажок у этой настройки (пусть только попробует не установить), то программа напомнит ему в нужный день о необходимости купить тортик.
Часто для этих целей в приложениях используют отдельный экран с настройками. Безусловно вы можете создать собственный xml-файл разметки и разместить там нужные элементы управления. Но Android для этих целей предоставляет собственный Preferences Framework, с помощью которого можно создавать индивидуальный набор предпочтений и встраивать их в приложения. Главное преимущество использования фреймворка состоит в том, что экран настроек выглядит стандартно во всех приложениях и позволяет пользователю быстрее разобраться в работе.
Кроме того фреймворк настроек позволяет прослушивать изменения в Общих настройках через реализацию метода onSharedPreferenceChangeListener().
Использование фреймворка позволяет быстро создать экран настроек практически без написания кода. Предпочтения — это отдельная активность в приложении, вызываемая из вашей активности. Сама активность настроек расширяется от класса PreferenceActivity, а не от стандартного класса Activity. Предпочтения определяются в отдельном XML-файле, где корневым элементом является элемент <PreferenceScreen>, который представляет собой контейнер для предпочтений и может содержать дочерние элементы <PreferenceScreen>. Элемент <PreferenceCategory> также является контейнерным элементом и предназначен для объединения предпочтений в группы.
Настройка внешнего вида разметки создаётся не в папке res/layout, а в папке res/xml. Если такой папки нет, то создайте её самостоятельно. Для начала необходимо создать в папке res/xml XML-файл ресурсов, например, settings.xml. В результате получим простейшую заготовку.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
</PreferenceScreen>
Далее нужно построить свой экран настроек, используя готовые компоненты.
Для сохранения предпочтений используются различные классы:
У элементов кроме своих собственных атрибутов есть обязательные атрибуты:
В созданном файле устанавливаем настройки. Начнём с CheckBoxPreference.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:defaultValue="true"
android:key="wifi"
android:summary="@string/wifi_summary"
android:title="@string/wifi_title"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="hints"
android:summary="@string/hints_summary"
android:title="@string/hints_title"/>
</PreferenceScreen>
Мы определили две настройки для программы:
В нашем случае мы выводим флажки (элемент CheckBoxPreference), которые по умолчанию будут отмечены (атрибут android:defaultValue).
Не забываем добавить новые строковые ресурсы в файл res/values/strings.xml:
<string name="wifi_title">Wi-Fi</string>
<string name="wifi_summary">Вкл. или выкл. сеть WiFi</string>
<string name="hints_title">Подсказки</string>
<string name="hints_summary">Показывать подсказки</string>
Далее создаём новый класс MyPreferenceActivity, который будет наследоваться от класса PreferenceActivity. Добавим знакомый метод onCreate(), только вместо setContentView() вызовем метод addPreferencesFromResource, который по сути делает такую же работу - берёт данные из XML-файла и строит внешний вид экрана активности:
package ru.alexanderklimov.preferencedemo
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class MyPreferenceActivity extends PreferenceActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
}
Метод addPreferencesFromResource() считывает установленные настройки из XML-файла, где хранятся наши ресурсы, и делает их доступными для программы. В результате мы должны увидеть настройки на экране. Не забываем зарегистрировать новую активность в манифесте:
<activity
android:name=".MyPreferenceActivity"
android:label="@string/settings_title">
</activity>
Ресурс settings_title подготовьте самостоятельно.
Напишем метод для вывода окна настроек, который можно повесить на щелчок мыши или пункта меню:
public void showSettings()
{
Intent intent = new Intent(this, MyPreferenceActivity.class);
startActivity(intent);
}
Запустите программу и вызовите окно настроек. Попробуйте снять или установить флажки в разных сочетаниях. Затем закройте приложение и снова запустите его. Если вы откроете окно настроек, то увидите, что сделанные вами изменения сохранены. Система сама делает за нас всю работу и запоминает установленные настройки.
Наверное, вы обратили внимание, что метод addPreferencesFromResource() был зачёркнут и помечен, как устаревший. В новых версиях Android следует использовать фрагмент PreferenceFragment, о котором поговорим в другой раз.
Мы научились сохранять настройки при помощи флажков. Существует также возможность сохранения настроек в текстовом виде при помощи EditTextPreference. Подобный способ настройки позволяет сохранять текст, вводимый пользователем. Давайте добавим возможность устанавливать размер шрифта для текста. Откроем снова файл settings.xml и добавим новый элемент EditTextPreference:
<EditTextPreference
android:key="test_size"
android:title="Размер шрифта"
android:summary="Устанавливает новый размер шрифта"
android:defaultValue="14"
android:dialogTitle="Введите размер шрифта (от 10 до 32)" />
Для удобства я буду сразу выводить строковые значения без использования строковых ресурсов. В своих рабочих проектах вам нужно использовать ресурсы обязательно.
Запустите проект и вызовите окно настроек. Теперь у нас появилась опция установки размера шрифта. Если щёлкнуть на созданном элементе, то откроется новое диалоговое окно с текстовым полем ввода.
В следующем уроке мы разберём, как считывать установленные значения размера шрифта.
Также можно использовать списки для хранения настроек. Для этой цели используется диалоговое окно ListPreference. Необходимо заранее подготовить строковый ресурс для заголовка и массив строк для самого списка значений. Индекс выбранной строки списка будет задавать нужное значение для сохранения в SharedPreferences.
Добавим список для выбора стиля текста. В списке будет четыре опции: Обычный, Полужирный, Курсив, Полужирный+Курсив.
Подготовим массив строк и сохраним его в файле arrays.xml, который необходимо поместить в каталог res/values/.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="text_style">
<item>Обычный</item>
<item>Полужирный</item>
<item>Курсив</item>
<item>Полужирный+Курсив</item>
</string-array>
</resources>
В файл настроек settings.xml добавим дополнительный элемент <ListPreference>, в котором определим атрибуты заголовка окна, привязку к массиву значений и значение по умолчанию:
<ListPreference
android:key="test_style"
android:title="Стиль для шрифта"
android:summary="Устанавливает стиль для шрифта"
android:defaultValue="1"
android:entries="@array/text_style"
android:entryValues="@array/text_style"
android:dialogTitle="Выберите стиль для шрифта" />
Код для чтения настроек из списка рассматривается в следующем уроке.
Запустив проект, вы теперь увидите новую настройку Стиль для шрифта, которая открывает диалоговое окно для выбора стиля из списка. Обратите внимание, что в диалоговом окне нет кнопки сохранения, только Отмена. Изменения сохраняются сразу при выборе опции списка.
Поняв общий принцип, вы можете сами разобраться с другими элементами настроек. Рассмотрим ещё один элемент - SwitchPreference, который является заменой для CheckBoxPreference и позволяет включать или выключать настройку.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<CheckBoxPreference
android:defaultValue="true"
android:key="wifi"
android:summary="@string/wifi_summary"
android:title="@string/wifi_title" />
<CheckBoxPreference
android:defaultValue="true"
android:key="hints"
android:summary="@string/hints_summary"
android:title="@string/hints_title" />
<SwitchPreference
android:key="glad"
android:summaryOff="Вы уверены, что не будете гладить кота?"
android:summaryOn="Гладить кота?"
android:switchTextOff="Нет"
android:switchTextOn="Да" />
</PreferenceScreen>
В результате получим:
В зависимости от выбранного варианта мы можем менять текст настройки через атрибуты summaryOn/summaryOff.
Рассмотрим работу с настройкой, связанной с установкой мелодии для звонка. Предпочтение <RingtonePreference> предоставляет диалоговое окно выбора мелодии звонка со списком опций. Список в диалоговом окне отображает мелодии для звонка, уведомлений, тонового набора, доступные на мобильном устройстве. Также предусмотрена возможность добавления дополнительной опции "Silent" (Тихий режим) - добавьте атрибут android:showSilent="true".
Создадим новый файл настроек res/xml/ringtone_preference.xml:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<RingtonePreference
android:key="setting_ringtone"
android:showDefault="true"
android:showSilent="true"
android:summary="Устанавливает мелодию для звонка (вкл. или выкл.)"
android:title="Настройки рингтонов"/>
</PreferenceScreen>
Создадим новую активность настроек для рингтонов RingtonePreferenceActivity, повторив шаги из предыдущего примера. Запустим созданную активность.
Intent intent = new Intent(this, RingtonePreferenceActivity.class);
startActivity(intent);
Если в приложении используется слишком много настроек разного типа, то можно сгруппировать их по категориям, чтобы улучшить внешний вид окна настроек. Для этого в Preferences Framework есть специальный элемент PreferenceCategory.
Еще раз вернёмся к статье Создание простого текстового редактора, где использовались настройки для открытия файла и для работы с текстом (Размер шрифта и стиль шрифта). Почему бы нам не разбить настройки на две категории: к первой категории мы отнесем настройку открытия файла, а ко второй две настройки, связанные с шрифтами.
Категории добавляются через элемент <PreferenceCategory> под корневым элементом <PreferenceScreen>, и в него уже помещаются нужные настройки:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Работа с файлами">
<CheckBoxPreference
android:key="pref_openmode"
android:title="Открыть файл"
android:summary="Открывать файл при запуске приложения" />
</PreferenceCategory>
<PreferenceCategory
android:title="Настройки шрифта">
<EditTextPreference
android:key="text_size"
android:title="Размер шрифта"
android:summary="Устанавливает новый размер шрифта"
android:defaultValue="14"
android:dialogTitle="Введите размер шрифта (от 10 до 32)" />
<ListPreference
android:key="text_style"
android:title="Стиль для шрифта"
android:summary="Устанавливает стиль для шрифта"
android:defaultValue="1"
android:entries="@array/text_style"
android:entryValues="@array/text_style"
android:dialogTitle="Выберите стиль для шрифта" />
</PreferenceCategory>
</PreferenceScreen>
Если вы посмотрите на экран настроек, то увидите дополнительную полоску с текстом "Настройки шрифта".
Кроме корневого элемента <PreferenceScreen> в файле настроек можно использовать вложенные дочерние элементы <PreferenceScreen>, которые будут отображаться в отдельном окне. Родительский экран <PreferenceScreen> в этом случае будет отображать вход для запуска дочернего экрана настроек. Продолжим опыты и добавим ещё одну настройку, управляющую цветом (выделено жирным).
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Работа с файлами">
<CheckBoxPreference
android:key="pref_openmode"
android:title="Открыть файл"
android:summary="Открывать файл при запуске приложения" />
</PreferenceCategory>
<PreferenceCategory
android:title="Настройки шрифта">
<EditTextPreference
android:key="text_size"
android:title="Размер шрифта"
android:summary="Устанавливает новый размер шрифта"
android:defaultValue="14"
android:dialogTitle="Введите размер шрифта (от 10 до 32)" />
<ListPreference
android:key="text_style"
android:title="Стиль для шрифта"
android:summary="Устанавливает стиль для шрифта"
android:defaultValue="1"
android:entries="@array/text_style"
android:entryValues="@array/text_style"
android:dialogTitle="Выберите стиль для шрифта" />
<PreferenceScreen
android:key="text_color"
android:title="Цвет текста"
android:summary="Устанавливает цвет для текста">
<CheckBoxPreference
android:key="text_color_black"
android:title="Черный"
android:defaultValue="true"
android:summary="Устанавливает черный цвет" />
<CheckBoxPreference
android:key="text_color_red"
android:title="Красный"
android:summary="Устанавливает красный цвет" />
<CheckBoxPreference
android:key="text_color_green"
android:title="Зеленый"
android:summary="Устанавливает зеленый цвет" />
<CheckBoxPreference
android:key="text_color_blue"
android:title="Синий"
android:summary="Устанавливает синий цвет" />
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>
Запустив приложение, вы увидите новую настройку Цвет текста, которая открывает новое окно для выбора цвета текста.
Вам остаётся только написать код для обработки выбора пользователя, чтобы вывести текст в текстовом редакторе в нужном цвете. Обратите внимание, что в нашем примере используются флажки, которые позволяют создавать комбинации различных цветов.
Исходный код для текстового редактора смотрите здесь
У фреймворка есть интересная особенность - можно внедрить в экран настроек вашего приложения вызов системных настроек. Делается это следующим образом. В файл настроек добавляем новый блок, например, такой:
<PreferenceScreen
android:summary="Импортированная системная настройка"
android:title="Настройка через намерение" >
<intent android:action="android.settings.AIRPLANE_MODE_SETTINGS" />
</PreferenceScreen>
Класс android.provider.Settings включает множество констант типа android.settings.*, которые можно использовать для вызова экранов системных настроек. Я выбрал настройку Автономного режима, также можно было вызвать настройку Экрана (android.settings.DISPLAY_SETTINGS). На экране настроек появится новый пункт, который автоматически сработает при нажатии.
При использовании данного способа система интерпретирует это как запрос на вызов метода startActivity() с указанным действием. Это очень удобно, когда из вашего экрана настроек нужно вызвать стандартную системную настройку.
Иногда требуется, чтобы часть настроек была связана с определённой родительской настройкой. И если родительская настройка заблокирована, то есть смысл автоматически заблокировать и дочерние настройки. Для этой цели применяется атрибут android:dependency.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="cat_key"
android:title="У вас есть кот?"
android:summary="Доступно для владельцев котов"
android:defaultValue="true" />
<EditTextPreference
android:key="catfood"
android:title="Подарки для кота"
android:summary="Угости кота"
android:dependency="cat_key" />
</PreferenceScreen>
Мы указали, что текстовое поле зависит от родительского флажка. Запустим и сравним поведение в двух разных случаях.
Как уже упоминалось, класс onSharedPreferenceChangeListener позволяет вызвать callback-метод в момент добавления, удаления или изменения конкретной Общей настройки. Используя этот обработчик, компоненты вашего приложения могут следить за изменениями в настройках, установленных пользователем, и обновлять пользовательский интерфейс или корректировать поведение программы.
Зарегистрируйте обработчик OnSharedPreferenceChangeListener, применив Общие настройки, за которыми вы хотите следить. Реализация onSharedPreferenceChanged() показана ниже (каркас):
public class MyActivity extends Activity implements OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle SavedInstanceState) {
// Регистрируем этот OnSharedPreferenceChangeListener
Context context = getApplicationContext();
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(context);
prefs.registerOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// TODO Проверять общие настройки, ключевые параметры и изменять UI
// или поведение приложения, если потребуется.
}
}
При создании экрана настроек мы полагаемся на систему. В одной из версий Android по умолчанию фон активности был чёрным, сейчас белый. Если вы хотите применить свой цвет, то добавьте строчку кода:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new MyPrefsFragment()).commit();
findViewById(android.R.id.list).setBackgroundColor(Color.MAGENTA);
}
Про PreferenceFragment рассказывается в конце статьи.
Если вы хотите поменять цвета у PreferenceCategory, то тут немного сложнее. Придётся создать отдельный класс, наследующий от PreferenceCategory и переопределить метод onCreateView:
package ru.alexanderklimov.test;
import android.content.Context;
import android.graphics.Color;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class CustomPreferenceCategory extends PreferenceCategory {
public CustomPreferenceCategory(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomPreferenceCategory(Context context) {
super(context);
}
@Override
protected View onCreateView(ViewGroup parent) {
TextView categoryTitle = (TextView)super.onCreateView(parent);
categoryTitle.setBackgroundColor(Color.WHITE);
categoryTitle.setTextColor(Color.RED);
return categoryTitle;
}
}
В файле разметки заменяем тег категории на свой:
<ru.alexanderklimov.test.CustomPreferenceCategory
android:title="Работа с файлами">
<CheckBoxPreference
android:key="pref_openmode"
android:title="Открыть файл"
android:summary="Открывать файл при запуске приложения" />
</ru.alexanderklimov.test.CustomPreferenceCategory>
Вот результат в дополнение к изменённому фону, где я поменял для наглядности и одну категорию.
По такому же принципу можно поменять и другие классы, в частности Preference.
Допустим, мы хотим настраивать параметр с помощью компонента SeekBar в диалоговом окне. Нам нужно наследоваться от класса DialogPreference и написать свой код.
Подготовим разметку.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text_dialog_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:paddingLeft="12dip"
android:paddingRight="12dip" >
</TextView>
<TextView
android:id="@+id/text_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:gravity="center_horizontal" >
</TextView>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginTop="6dip" />
</LinearLayout>
Подготовим атрибуты. Обратите внимание, что для некоторых атрибутов мы не используем свойства из пакета android.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- note: to re-use an existing Android attribute not already used by the superclass, name should have prefix "android:" and do not define a format -->
<declare-styleable name="SeekBarDialogPreference">
<attr name="android:max" />
<attr name="min" format="integer" />
<attr name="progressTextSuffix" format="string" />
</declare-styleable>
</resources>
Напишем новый класс.
package ru.alexanderklimov.test;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class SeekBarDialogPreference extends DialogPreference {
private static final int DEFAULT_MIN_PROGRESS = 0;
private static final int DEFAULT_MAX_PROGRESS = 100;
private static final int DEFAULT_PROGRESS = 0;
private int mMinProgress;
private int mMaxProgress;
private int mProgress;
private CharSequence mProgressTextSuffix;
private TextView mProgressText;
private SeekBar mSeekBar;
public SeekBarDialogPreference(Context context) {
this(context, null);
}
public SeekBarDialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
// get attributes specified in XML
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SeekBarDialogPreference, 0, 0);
try {
setMinProgress(a.getInteger(
R.styleable.SeekBarDialogPreference_min,
DEFAULT_MIN_PROGRESS));
setMaxProgress(a.getInteger(
R.styleable.SeekBarDialogPreference_android_max,
DEFAULT_MAX_PROGRESS));
setProgressTextSuffix(a
.getString(R.styleable.SeekBarDialogPreference_progressTextSuffix));
} finally {
a.recycle();
}
// set layout
setDialogLayoutResource(R.layout.preference_seek_bar_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
setProgress(restore ? getPersistedInt(DEFAULT_PROGRESS)
: (Integer) defaultValue);
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index, DEFAULT_PROGRESS);
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
TextView dialogMessageText = (TextView) view
.findViewById(R.id.text_dialog_message);
dialogMessageText.setText(getDialogMessage());
mProgressText = (TextView) view.findViewById(R.id.text_progress);
mSeekBar = (SeekBar) view.findViewById(R.id.seek_bar);
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// update text that displays the current SeekBar progress value
// note: this does not persist the progress value. that is only
// ever done in setProgress()
String progressStr = String.valueOf(progress + mMinProgress);
mProgressText.setText(mProgressTextSuffix == null ? progressStr
: progressStr.concat(mProgressTextSuffix.toString()));
}
});
mSeekBar.setMax(mMaxProgress - mMinProgress);
mSeekBar.setProgress(mProgress - mMinProgress);
}
public int getMinProgress() {
return mMinProgress;
}
public void setMinProgress(int minProgress) {
mMinProgress = minProgress;
setProgress(Math.max(mProgress, mMinProgress));
}
public int getMaxProgress() {
return mMaxProgress;
}
public void setMaxProgress(int maxProgress) {
mMaxProgress = maxProgress;
setProgress(Math.min(mProgress, mMaxProgress));
}
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
progress = Math.max(Math.min(progress, mMaxProgress), mMinProgress);
if (progress != mProgress) {
mProgress = progress;
persistInt(progress);
notifyChanged();
}
}
public CharSequence getProgressTextSuffix() {
return mProgressTextSuffix;
}
public void setProgressTextSuffix(CharSequence progressTextSuffix) {
mProgressTextSuffix = progressTextSuffix;
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
// when the user selects "OK", persist the new value
if (positiveResult) {
int seekBarProgress = mSeekBar.getProgress() + mMinProgress;
if (callChangeListener(seekBarProgress)) {
setProgress(seekBarProgress);
}
}
}
@Override
protected Parcelable onSaveInstanceState() {
// save the instance state so that it will survive screen orientation
// changes and other events that may temporarily destroy it
final Parcelable superState = super.onSaveInstanceState();
// set the state's value with the class member that holds current
// setting value
final SavedState myState = new SavedState(superState);
myState.minProgress = getMinProgress();
myState.maxProgress = getMaxProgress();
myState.progress = getProgress();
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
// check whether we saved the state in onSaveInstanceState()
if (state == null || !state.getClass().equals(SavedState.class)) {
// didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// restore the state
SavedState myState = (SavedState) state;
setMinProgress(myState.minProgress);
setMaxProgress(myState.maxProgress);
setProgress(myState.progress);
super.onRestoreInstanceState(myState.getSuperState());
}
private static class SavedState extends BaseSavedState {
int minProgress;
int maxProgress;
int progress;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
minProgress = source.readInt();
maxProgress = source.readInt();
progress = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(minProgress);
dest.writeInt(maxProgress);
dest.writeInt(progress);
}
@SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
Добавляем в разметку экрана настройки новый компонент. Обратите внимание на третью строчку, которая позволяют задавать собственные атрибуты custom: вместо android:.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<CheckBoxPreference
android:defaultValue="true"
android:key="wifi"
android:summary="@string/wifi_summary"
android:title="@string/wifi_title" />
<CheckBoxPreference
android:defaultValue="true"
android:key="hints"
android:summary="@string/hints_summary"
android:title="@string/hints_title" />
<SwitchPreference
android:key="glad"
android:summaryOff="Вы уверены, что не будете гладить кота?"
android:summaryOn="Гладить кота?"
android:switchTextOff="Нет"
android:switchTextOn="Да" />
<ru.alexanderklimov.test.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Выберите нужное значение:"
android:key="height"
android:max="50"
android:summary="Высота кота в см"
android:title="Рост кота"
custom:min="1"
custom:progressTextSuffix=" см" />
</PreferenceScreen>
Запускаем приложение. Видим настройку для установки роста кота. Нажимаем на него для запуска диалогового окна, в котором с помощью ползунка можем выбрать нужный размер.
Чтобы получить установленные значения, напишем код в главной активности.
// Кнопка для запуска экрана настроек
public void onClick(View v){
showSettings();
}
public void showSettings() {
Intent intent = new Intent(MainActivity.this, PrefsActivity.class);
startActivityForResult(intent, 0);
}
// получаем результат
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(this);
int catHeight = sharedPreferences.getInt("height", 20);
// Добавим TextView, в котором будем выводить значение настройки
settingCheckBox.setText("Высота кота = "
+ catHeight);
}
Создадим диалоговое окно настройки с использованием NumberPicker.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text_dialog_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:paddingLeft="12dip"
android:paddingRight="12dip" >
</TextView>
<NumberPicker
android:id="@+id/number_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dip"
android:layout_marginTop="6dip" />
</LinearLayout>
Если вы будете использовать пример вместе с предыдущим примером SeekBarDialogPreference, то данный файл не понадобится, так как эти атрибуты уже у вас есть.
<resources>
<!-- note: to re-use an existing Android attribute not already used by the superclass, name should have prefix "android:" and do not define a format -->
<declare-styleable name="NumberPickerDialogPreference">
<attr name="android:max" />
<attr name="min" format="integer" />
</declare-styleable>
</resources>
Если вы используете пример вместе с предыдущим и не меняли файл attr.xml, то в коде придётся заменить некоторые строчки. По ошибкам вы поймёте, что нужно поменять.
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.TextView;
public class NumberPickerDialogPreference extends DialogPreference
{
private static final int DEFAULT_MIN_VALUE = 0;
private static final int DEFAULT_MAX_VALUE = 100;
private static final int DEFAULT_VALUE = 0;
private int mMinValue;
private int mMaxValue;
private int mValue;
private NumberPicker mNumberPicker;
public NumberPickerDialogPreference(Context context)
{
this(context, null);
}
public NumberPickerDialogPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
// get attributes specified in XML
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberPickerDialogPreference, 0, 0);
try
{
setMinValue(a.getInteger(R.styleable.NumberPickerDialogPreference_min, DEFAULT_MIN_VALUE));
setMaxValue(a.getInteger(R.styleable.NumberPickerDialogPreference_android_max, DEFAULT_MAX_VALUE));
}
finally
{
a.recycle();
}
// set layout
setDialogLayoutResource(R.layout.preference_number_picker_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue)
{
setValue(restore ? getPersistedInt(DEFAULT_VALUE) : (Integer) defaultValue);
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index)
{
return a.getInt(index, DEFAULT_VALUE);
}
@Override
protected void onBindDialogView(View view)
{
super.onBindDialogView(view);
TextView dialogMessageText = (TextView) view.findViewById(R.id.text_dialog_message);
dialogMessageText.setText(getDialogMessage());
mNumberPicker = (NumberPicker) view.findViewById(R.id.number_picker);
mNumberPicker.setMinValue(mMinValue);
mNumberPicker.setMaxValue(mMaxValue);
mNumberPicker.setValue(mValue);
}
public int getMinValue()
{
return mMinValue;
}
public void setMinValue(int minValue)
{
mMinValue = minValue;
setValue(Math.max(mValue, mMinValue));
}
public int getMaxValue()
{
return mMaxValue;
}
public void setMaxValue(int maxValue)
{
mMaxValue = maxValue;
setValue(Math.min(mValue, mMaxValue));
}
public int getValue()
{
return mValue;
}
public void setValue(int value)
{
value = Math.max(Math.min(value, mMaxValue), mMinValue);
if (value != mValue)
{
mValue = value;
persistInt(value);
notifyChanged();
}
}
@Override
protected void onDialogClosed(boolean positiveResult)
{
super.onDialogClosed(positiveResult);
// when the user selects "OK", persist the new value
if (positiveResult)
{
int numberPickerValue = mNumberPicker.getValue();
if (callChangeListener(numberPickerValue))
{
setValue(numberPickerValue);
}
}
}
@Override
protected Parcelable onSaveInstanceState()
{
// save the instance state so that it will survive screen orientation changes and other events that may temporarily destroy it
final Parcelable superState = super.onSaveInstanceState();
// set the state's value with the class member that holds current setting value
final SavedState myState = new SavedState(superState);
myState.minValue = getMinValue();
myState.maxValue = getMaxValue();
myState.value = getValue();
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
// check whether we saved the state in onSaveInstanceState()
if (state == null || !state.getClass().equals(SavedState.class))
{
// didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// restore the state
SavedState myState = (SavedState) state;
setMinValue(myState.minValue);
setMaxValue(myState.maxValue);
setValue(myState.value);
super.onRestoreInstanceState(myState.getSuperState());
}
private static class SavedState extends BaseSavedState
{
int minValue;
int maxValue;
int value;
public SavedState(Parcelable superState)
{
super(superState);
}
public SavedState(Parcel source)
{
super(source);
minValue = source.readInt();
maxValue = source.readInt();
value = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
super.writeToParcel(dest, flags);
dest.writeInt(minValue);
dest.writeInt(maxValue);
dest.writeInt(value);
}
@SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
@Override
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
}
Добавляем на экран настроек:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<SwitchPreference
android:key="glad"
android:summaryOff="Вы уверены, что не будете гладить кота?"
android:summaryOn="Гладить кота?"
android:switchTextOff="Нет"
android:switchTextOn="Да" />
<ru.alexanderklimov.test.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Выберите нужное значение:"
android:key="height"
android:max="50"
android:summary="Высота кота в см"
android:title="Рост кота"
custom:min="1"
custom:progressTextSuffix=" см" />
<ru.alexanderklimov.test.NumberPickerDialogPreference
android:defaultValue="200"
android:dialogMessage="Выберите суточный паёк для кота в граммах:"
android:max="250"
android:title="Печёнка для кота (гр)"
custom:min="100" />
</PreferenceScreen>
В Android есть секретный класс VolumePreference. Он как бы есть, но в редакторе настроек его нет, и вы не можете добавить его в XML-файл. Можно это сделать вручную. Впрочем, не обольщайтесь. Положение ползунка программа запоминает, но на уровень громкости устройства это никак не влияет, так как эта настройка системная и через приложение управлять ей нельзя. Также нельзя получить выбранные данные. Показываю пример просто для шутки.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<SwitchPreference
android:key="glad"
android:summaryOff="Вы уверены, что не будете гладить кота?"
android:summaryOn="Гладить кота?"
android:switchTextOff="Нет"
android:switchTextOn="Да" />
<VolumePreference
android:name="Volume Preference"
android:key="ringVolPref"
android:summary="Установите нужную громкость мяуканья кота"
android:title="Настройка громкости звука" />
</PreferenceScreen>
Так это выглядит на экране.
В старых версиях Android использовался класс PreferenceActivity с вызовом метода addPreferencesFromResource() для создания экрана настроек. Теперь подобный подход считается устаревшим и ему на смену пришёл класс PreferenceFragment (API 11).
Чтобы увидеть отличия, постараюсь максимально близко использовать старый проект.
Для начала создадим файл settings.xml в папке res/xml, как мы это делали и раньше.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:defaultValue="true"
android:key="wifi"
android:summary="@string/wifi_summary"
android:title="@string/wifi_title"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="hints"
android:summary="@string/hints_summary"
android:title="@string/hints_title"/>
</PreferenceScreen>
Строковые ресурсы остаются те же:
<string name="wifi_title">Wi-Fi</string>
<string name="wifi_summary">Вкл. или выкл. сеть WiFi</string>
<string name="hints_title">Подсказки</string>
<string name="hints_summary">Показывать подсказки</string>
Теперь вместо активности настроек PreferenceActivity нужно создать фрагмент настроек PreferenceFragment. Создаём новый класс:
package ru.alexanderklimov.preferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
public class MyPrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
}
Как видите, код практически тот же, только метод addPreferencesFromResource() относится уже не классу PreferenceActivity, а к классу PreferenceFragment.
Теперь в активности настроек, которая будет служить контейнером для фрагмента настроек, заменяем устаревший метод:
package ru.alexanderklimov.preferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class MyPreferenceActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// addPreferencesFromResource(R.xml.settings); // устаревший метод
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new MyPrefsFragment())
.commit();
}
}
Получается, что мы перенесли код создания экрана настроек в фрагмент, а в активности просто подключаем этот фрагмент. Вот и вся принципиальная разница.
Если вы хотите поддерживать новые и старые версии настроек, то можно переписать код следующим образом:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
addPreferencesFromResource(R.xml.preferences);
} else {
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new MyPrefsFragment())
.commit();
}
}
Также можно использовать вариант выбора нужного класса и его запуска через намерение:
Class c = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ?
MyPreferencesActivity1.class : MyPreferencesActivity2.class;
Intent intent = new Intent(this, c);
startActivityForResult(intent, SHOW_PREFERENCES);
Осталось только вызвать экран настроек. В старом примере использовался метод showSettings(), его и оставляем и для нового примера:
public void showSettings() {
Intent intent = new Intent(MainActivity.this, MyPreferenceActivity.class);
startActivityForResult(intent, 0);
}
// получаем результат
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(this);
boolean prefCheckBox = sharedPreferences.getBoolean("wifi",
true);
Log.i("Preference", "Настройки WiFI: "
+ String.valueOf(prefCheckBox));
}
Чисто внешне ничего не изменилось, поэтому скриншот приводить не буду.
Приведённый выше пример носит скорее косметический характер, мы просто подключили фрагмент. Но появилось и новшество - использование заголовков (Preference Headers), когда пользователь видит на экране список заголовков, которые в свою очередь запускают отдельные фрагменты. Интересная особенность у данного решения - на больших экранах автоматически включается двухпанельный режим просмотра (у меня не заработало). Рассмотрим пример.
Создадим новый файл с применением заголовков в папке res/xml.
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
<header
android:fragment="ru.alexanderklimov.test.UpdatePreferenceFragment"
android:summary="@string/settings_cathouse_summary"
android:title="@string/settings_cathouse" >
</header>
<header
android:fragment="ru.alexanderklimov.test.DisplayPreferenceFragment"
android:summary="@string/settings_displaySummary"
android:title="@string/settings_display" >
</header>
<header
android:fragment="ru.alexanderklimov.test.NotifyPreferenceFragment"
android:title="@string/settings_notify" >
</header>
</preference-headers>
В каждом теге header мы задали атрибут android:fragment, указывающий на экземпляр класса PreferenceFragment, который должен открываться при выборе конкретного заголовка. В нашем примере заданы три заголовка.
Строковые ресурсы (придумайте свои):
<string name="settings_cathouse">Кошкин дом</string>
<string name="settings_cathouse_summary">Этажность, техника</string>
<string name="settings_display">Презентация</string>
<string name="settings_displaySummary">Гости, подарки</string>
<string name="settings_notify">Уведомления</string>
Для загрузки заголовков понадобится активность, наследующая от PreferenceActivity. В ней нужно реализовать метод обратного вызова onBuildHeaders() и вызвать метод loadHeadersFromResource():
package ru.alexanderklimov.test;
import android.preference.PreferenceActivity;
import java.util.List;
public class HeaderPrefsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.header_settings, target);
}
// новое требование для API 19
protected boolean isValidFragment(String fragmentName) {
return UpdatePreferenceFragment.class.getName().equals(fragmentName);
/*
return MyPreferenceFragmentA.class.getName().equals(fragmentName)
|| MyPreferenceFragmentB.class.getName().equals(fragmentName)
|| // ... Finish with your last fragment.
*/
}
}
Обратите внимание, что в классе активности не требуется вызывать метод onCreate(), от активности требуется только загрузить заголовки (не забудьте прописать в манифесте).
В API 19 появилось какое-то глупое требование добавить проверку на валидность фрагмента. Просто добавьте в метод isValidFragment() используемые фрагменты. Без этого кода вылезает ошибка.
Займёмся созданием фрагмента настроек, который состоит из java-файла и разметки:
package ru.alexanderklimov.test;
import android.os.Bundle;
import android.preference.PreferenceFragment;
public class UpdatePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference_update);
}
}
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="@string/settings_cathouse" >
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_vacuum"
android:summary="@string/settings_vacuumcleaner_summary"
android:title="@string/settings_vacuumcleaner" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_floor"
android:summary="@string/settings_floor_summary"
android:title="@string/settings_floor" />
<ListPreference
android:defaultValue="3"
android:dependency="pref_floor"
android:dialogTitle="@string/settings_floorDialogTitle"
android:entries="@array/floor_options"
android:entryValues="@array/floor_values"
android:key="pref_floors"
android:title="@string/settings_floors" />
</PreferenceCategory>
</PreferenceScreen>
По аналогии создайте два других фрагмента для остальных заголовков настроек.
Теперь, если щёлкнуть по заголовку настроек, то откроется соответствующий фрагмент.
Обратите внимание, тег ListPreference зависит от второго флажка (android:dependency="pref_floor"). Если снять флажок, то список становится неактивным и выбрать количество этажей становится невозможным.
Для вызова активности настроек используется стандартный код:
public void onHeaderClick(View v){
Intent intent = new Intent(this, HeaderPrefsActivity.class);
startActivityForResult(intent, SHOW_PREFERENCES);
}
На больших экранах окно настроек автоматически становится двухпанельным. Но в эмуляторе для Nexus 7 данный способ у меня не сработал. Насколько я понял, устройство должно иметь разрешение sw720dp, чтобы включился двухпанельный режим. Если вы хотите иметь такой режим на меньших экранах, то вам придётся самостоятельно писать код для создания двухпанельного режима.
Чтобы увидеть двухпанельный режим в эмуляторе, пришлось создавать новое устройство - планшет на 10 дюймов.
Добавим в файл настроек settings.xml отдельный блок с указанием сайта.
<PreferenceCategory android:title="Запуск сайта через Intent" >
<PreferenceScreen
android:summary="developer.alexanderklimov.ru/android"
android:title="Освой Android играючи" >
<intent
android:action="android.intent.action.VIEW"
android:data="http://developer.alexanderklimov.ru/android/" />
</PreferenceScreen>
</PreferenceCategory>
Теперь, если пользователь в окне настроек щёлкнет по названию сайта, то запустится браузер с указанной страницей.
По такому же принципу вы можете вызвать любую активность и в заголовках. Тег intent будет расцениваться как запрос на вызов метода startActivity() с указанным действием.
<header
android:icon="..."
android:summary="">
<intent android:action="android.settings.DISPLAY_SETTINGS" />
</header>
Когда мы используем ListPreference, то пользователю приходится нажимать на раздел настроек для вызова диалогового окна со списком, чтобы увидеть текущий выбор. Если пользователь хочет выбрать новое значение, то данный шаг оправдан. Но если ему нужно только получить информацию, то почему бы её не вывести прямо в подсказку (Summary)? Для этого можно воспользоваться методом setSummary().
Подготовим список настроек в файле settings.xml:
<ListPreference
android:defaultValue="1"
android:dialogTitle="Выберите стиль для шрифта"
android:entries="@array/text_style"
android:entryValues="@array/text_style"
android:key="pref_style"
android:summary="Устанавливает стиль для шрифта"
android:title="Стиль для шрифта" />
Обратите внимание на атрибут android:key, он нам ещё пригодится в коде.
Добавим массив строк для настроек стиля шрифта:
<string-array name="text_style">
<item>Обычный</item>
<item>Полужирный</item>
<item>Курсив</item>
<item>Полужирный+Курсив</item>
</string-array>
Теперь напишем код для фрагмента, наследующего от PreferenceFragment:
package ru.alexanderklimov.preferences;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.PreferenceFragment;
public class MyPrefsFragment extends PreferenceFragment implements
OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
updateListPrefSummary();
}
@Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
// Apply for ListPreference with key="pref_style"
private void updateListPrefSummary() {
ListPreference preference = (ListPreference) findPreference("pref_style");
CharSequence entry = ((ListPreference) preference).getEntry();
preference.setSummary("Текущая настройка: " + entry);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
// if changed SharedPreference is ListPreference with key="pref_style",
// update summary
if (key.equals("pref_style")) {
updateListPrefSummary();
}
}
}
Теперь пользователь сразу видит текущую настройку стиля шрифта. Удобно же.
Если вы зайдёте в Настройки | Передача данных и выберите свою программу, то внизу увидите неактивированную кнопку Настройки приложения. Чтобы её активировать, пропишите в манифесте у свой активности, которая отвечает за настройки, специальный фильтр MANAGE_NETWORK_USAGE:
<activity android:name="PrefsActivity" >
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" >
</action>
<category android:name="android.intent.category.DEFAULT" >
</category>
</intent-filter>
</activity>