Приложение часто включает настройки, которые позволяют пользователю изменять его функции и поведение. Например, некоторые приложения позволяют разрешить или запретить уведомления или настроить интервал синхронизации с сервером.
Если вы хотите добавить в приложение окно настроек, используйте класс Preference.
Дизайн настроек. Информация о настройке внешнего вида настроек находится в разделе Настройки руководства по дизайну.
Рисунок 1. Окно настроек приложения Android.
Обзор
Для создания интерфейса настроек используются различные подклассы Preference, а не объекты типа View.
Объект Preference это строительный блок для одной опции. Каждый объект Preference появляется в виде пункта списка настроек. Например, CheckBoxPreference создает флажок, а ListPreference диалоговое окно со списком вариантов.
Кадлый объект Preference имеет соответствующую пару ключ-значение, которую система использует для сохранения опции в файл SharedPreferences, который используется по умолчанию для хранения настроек приложения. Когда пользователь меняет значение опции, система автоматически обновляет соответствующее значение в файле SharedPreferences. Вы должны напрямую взаимодействовать с файлом SharedPreferences, только если необходимо прочитать значение опции и на ее основе определить поведение приложения.
Значения, хранимые в объекте SharedPreferences могут иметь следующие типы:
- Boolean
- Float
- int
- Long
- String
- String Set
Поскольку ваше приложение использует для построения интерфейса настроек объекты типа Preference, вместо View, необходимо использовать специальный подкласс Activity или Fragment:
- Если приложение создается для Android версии ниже 3.0, вы должны использовать класс PreferenceActivity.
- Для Android 3.0 и выше используйте традиционный класс Activity, содержащий фрагмент PreferenceFragment, который отображает настройки. Однако, вы можете также использовать класс PreferenceActivity для создания двух-панельной разметки на больших экранах, если у вас есть несколько групп настроек.
Компоненты настроек
Каждая настройка приложения представлена в виде подкласса Preference. Каждый подкласс включает набор свойств, позволяющих указать такие вещи, как заголовок настройки или значение по умолчанию. Каждый подкласс также предоставляет собственный внешний вид настройки. Например, на рисунке 1 каждый пункт окна настроек представлен отдельным объектом типа Preference.
Вот наиболее часто используемые подклассы:
- CheckBoxPreference
- Показывает пункт настройки в виде флажка, который может быть установлен или снят. Значение имеет тип boolean (
true
, если флажок установлен). - ListPreference
- Открывает диалог со списком радио-кнопок. Значение может иметь любой из поддерживаемых типов, которые перечислены выше.
- EditTextPreference
- Открывает диалог с текстовым полем EditText. Значение имеет тип String.
Полный список доступных компонентов и их свойств смотрите в документации к классу Preference.
Разумеется, встроенные классы могут не учитывать все потребности и вашему приложению может потребоваться что-то особенное. Например, Android не предоставляет компонентов для установки даты и времени в качестве настройки. В таких случаях вы можете создать собственный подкласс Preference. Об это мы поговорим ниже.
Создание настроек в XML
Хотя вы можете создавать экземпляры объектов Preference во время выполнения приложения, лучше создавать элементы настроек в XML. XML файл обеспечивает простую структуру, которую при необходимости легко обновлять, тем более, что список настроек обычно известен заранее.
Каждый подкласс Preference имеет соответствующий тег, вроде <CheckBoxPreference>
.
XML файл необходимо сохранить в директорию res/xml/
. Вы можете использовать произвольное имя для файла, хотя принято называть его preferences.xml
. Обычно требуется один файл, поскольку для создания ветвей дерева настроек используется вложенный объект PreferenceScreen.
Примечание: если вы хотите создать много-панельную разметку для настроек, необходимо использовать разные файлы для каждого из фрагментов.
Корневым узлом XML файла должен быть элемент <PreferenceScreen>. Каждый дочерний элемент представляет отдельный пункт в списке настроек.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="pref_sync" android:title="@string/pref_sync" android:summary="@string/pref_sync_summ" android:defaultValue="true" /> <ListPreference android:dependency="pref_sync" android:key="pref_syncConnectionType" android:title="@string/pref_syncConnectionType" android:dialogTitle="@string/pref_syncConnectionType" android:entries="@array/pref_syncConnectionTypes_entries" android:entryValues="@array/pref_syncConnectionTypes_values" android:defaultValue="@string/pref_syncConnectionTypes_default" /> </PreferenceScreen> |
В данном примере элементы CheckBoxPreference и ListPreference имеют следующие атрибуты:
android:key
Это обязательный атрибут, который содержит уникальный строковый ключ, используемый системой для сохранения значения в объект SharedPreferences.
Атрибут не требуется только для объектов PreferenceCategory и PreferenceScreen, а также Intent или Fragment.
android:title
- Заголовок опции, который виден пользователю.
android:defaultValue
- Указывает начальное значение опции, который система устанавливает в файле SharedPreferences. Всегда указывайте начальное значение для всех опций!
Создание группы настроек

Рисунок 2. Группы настроек.
Если список настроек включает 10 пунктов и более, пользователи могут среди них потеряться. Чтобы помочь ориентироваться в множестве настроек, можно разделить список на группы. Существует два способа создания групп:
- Заголовок группы
- Отдельный экран
Вы можете комбинировать оба способа. Решая, какой способ использовать и как разделить настройки, следуйте руководству по дизайну настроек.
Заголовки групп
Чтобы разделить группы заголовками (как на рисунке 2), поместите каждую группу в объект PreferenceCategory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="@string/pref_sms_storage_title" android:key="pref_key_storage_settings"> <CheckBoxPreference android:key="pref_key_auto_delete" android:summary="@string/pref_summary_auto_delete" android:title="@string/pref_title_auto_delete" android:defaultValue="false"... /> <Preference android:key="pref_key_sms_delete_limit" android:dependency="pref_key_auto_delete" android:summary="@string/pref_summary_delete_limit" android:title="@string/pref_title_sms_delete"... /> <Preference android:key="pref_key_mms_delete_limit" android:dependency="pref_key_auto_delete" android:summary="@string/pref_summary_delete_limit" android:title="@string/pref_title_mms_delete" ... /> </PreferenceCategory> ... </PreferenceScreen> |
Отдельный экран настроек
Для того, чтобы поместить группу настроек на отдельный экран (как показано на рисунке 3), расположите ее внутри объекта PreferenceScreen.
Рисунок 3. Отдельный экран для группы настроек.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- opens a subscreen of settings --> <PreferenceScreen android:key="button_voicemail_category_key" android:title="@string/voicemail" android:persistent="false"> <ListPreference android:key="button_voicemail_provider_key" android:title="@string/voicemail_provider" ... /> <!-- opens another nested subscreen --> <PreferenceScreen android:key="button_voicemail_setting_key" android:title="@string/voicemail_settings" android:persistent="false"> ... </PreferenceScreen> <RingtonePreference android:key="button_voicemail_ringtone_key" android:title="@string/voicemail_ringtone_title" android:ringtoneType="notification" ... /> ... </PreferenceScreen> ... </PreferenceScreen> |
Использование намерений
В некоторых случаях вы можете при выборе пункта настроек открывать другое явление, например web-браузер. Чтобы вызвать намерение при выборе пункта настроек, добавьте дочерний элемент <intent>
в соответствующий элемент <Preference>
.
Например, так вы можете открыть браузер:
1 2 3 4 |
<Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www.example.com" /> </Preference> |
Вы можете создавать явные и неявные намерения, используя следующие атрибуты:
android:action
- Назначает действие, в соответствии с методом setAction().
android:data
- Назначает данные, в соответствии с методом setData().
android:mimeType
- Указывает MIME тип, в соответствии с методом setType().
android:targetClass
- Наименование класса компонента, в соответствии с методом setComponent().
android:targetPackage
- Наименование пакета компонента, в соответствии с методом setComponent().
Создание явления настроек
Чтобы отобразить настройки в явлении, создайте подкласс PreferenceActivity. Это расширение традиционного класса Activity, отображающее список настроек на основе иерархии объектов типа Preference. PreferenceActivity автоматически сохраняет настройки, ассоциированные с каждым из объектов Preference при их изменении.
Примечание: если вы разрабатываете приложение для Android 3.0 или выше, вы должны использовать PreferenceFragment. Об этом рассказано в следующем параграфе.
Важнее всего запомнить, что для загрузки разметки во время выполнения метода onCreate() используется метод addPreferencesFromResource(). Пример минимального кода, который требуется для работы PreferenceActivity:
1 2 3 4 5 6 7 |
public class SettingsActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } } |
Этого достаточно для большинства приложений, поскольку как только пользователь изменит опцию, система сразу сохранит ее в файл настроек приложения SharedPreferences. Однако, многим приложениям требуется чуть больше кода для того, чтобы изменить поведение в ответ на изменение настроек. Этот случай мы рассмотрим ниже по тексту.
Использование фрагментов для отображения настроек
Если вы пишете приложение для Android 3.0 и выше, вы должны использовать для отображения списка настроек класс PreferenceFragment. Вы можете добавить PreferenceFragment в любое явление, для этого не нужно использовать PreferenceActivity.
Фрагменты предоставляют более гибкую архитектуру, по сравнению с использованием только отдельных явлений.
Ваша реализация подкласса PreferenceFragment может просто содержать метод onCreate() для загрузки настроек, используя addPreferencesFromResource():
1 2 3 4 5 6 7 8 9 10 |
public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Загрузка настроек из XML ресурса addPreferencesFromResource(R.xml.preferences); } ... } |
После этого вы можете добавить созданный фрагмент в явление, так же как и любой другой:
1 2 3 4 5 6 7 8 9 10 11 |
public class SettingsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Показать фрагмент. getFragmentManager().beginTransaction() .replace(android.R.id.content, new SettingsFragment()) .commit(); } } |
Примечание: PreferenceFragment не содержит собственного объекта Context. Если вам нужен контекст, вы можете вызвать метод getActivity(), но только в случае, если фрагмент присоединен к явлению. Когда фрагмент не присоединен, метод getActivity() возвращает null.
Установка значений по умолчанию
Настройки, вероятно, влияют на работу приложения, поэтому важно при первом запуске приложения инициализировать файл SharedPreferences стандартными значениями для каждого объекта Preference.
Первым делом нужно указать значения по умолчанию для элементов XML файла, используя атрибут android:defaultValue
. Значение может иметь любой тип, который подходит для соответствующего объекта Preference. Например:
1 2 3 4 5 6 7 8 9 |
<!-- default value is a boolean --> <CheckBoxPreference android:defaultValue="true" ... /> <!-- default value is a string --> <ListPreference android:defaultValue="@string/pref_syncConnectionTypes_default" ... /> |
Затем из метода onCreate() главного явления необходимо вызвать метод setDefaultValues():
1 |
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false); |
Вызов метода внутри onCreate()
гарантирует, что настройки по умолчанию инициализированы и могут быть прочитаны для определения поведения приложения.
Метод принимает три аргумента:
- Context (контекст) приложения
- Идентификатор XML ресурса с настройками, для которого устанавливаются настройки по умолчанию.
Флаг, показывающий, должны ли настройки по умолчанию устанавливаться единажды.
Если
false
, система установит настройки по умолчанию только если данный метод никогда ранее не вызывался (то есть свойство KEY_HAS_SET_DEFAULT_VALUES в файле настроек имеет значение false).
Передав false
в качестве последнего аргумента вы можете безопасно вызывать данный метод при каждом запуске явления, при этом настройки не будут перезаписаны. Однако, если передать true
, все настройки пользователя будут утеряны при перезапуске явления.
Использование заголовков настроек
В отдельных случаях, вы можете использовать первый экран настроек только в качестве списка для перехода к другим экранам. При использовании Android 3.0 и выше используйте для этого новую функцию “заголовки”, вместо вложенных элементов PreferenceScreen.
Для использования заголовков необходимо:
- Поместить каждую группу в отдельный экземпляр PreferenceFragment. То есть каждая группа должны храниться в отдельном XML файле.
- Создать XML файл заголовков, в котором перечислены все группы настроек и описано, какие фрагменты содержит соответствующий пункт списка.
- Создать подкласс PreferenceActivity для отображения настроек.
- Реализовать метод обратного вызова onBuildHeader(), чтобы указать файл заголовков.
Большим преимуществом использования PreferenceActivity является автоматическое разделение разметки на две панели при запуске на больших экранах.
Если приложение поддерживает Android ниже 3.0, вы можете использовать PreferenceFragment для создания двух-панельного интерфейса на новых устройствах, в то же время поддерживая традиционную много-экранную архитектуру на старых. Об этом немного ниже.
Рисунок 4. две панели, при использовании заголовков.
Рисунок 5. использование заголовков на смартфонах. При выборе пункта настроек, открывается соответствующий фрагмент.
Создание файла заголовков
Каждая группа настроек в списке заголовков создается с помощью элемента <header>
внутри родительского контейнера <preference-headers>
. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="utf-8"?> <preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne" android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" /> <header android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo" android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" > <!-- Пары ключ/значение могут быть включены во фрагмент как аргументы. --> <extra android:name="someKey" android:value="someHeaderValue" /> </header> </preference-headers> |
С помощью атрибута android:fragment
каждый заголовок указывает на экземпляр PreferenceFragment, который должен открываться при выборе заголовка.
Элемент <extras>
позволяет передать пары ключ-значение во фрагмент внутри объекта типа Bundle. Фрагмент может получить аргументы с помощью метода getArguments(). Вы можете использовать один и тот же подкласс PreferenceFragment для всех групп, передавая в качестве аргумента название XML файла настроек, который должен быть загружен во фрагмент.
Например, мы используем аргумент с ключем "settings"
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String settings = getArguments().getString("settings"); if ("notifications".equals(settings)) { addPreferencesFromResource(R.xml.settings_wifi); } else if ("sync".equals(settings)) { addPreferencesFromResource(R.xml.settings_sync); } } } |
Отображение заголовков
Для отображения заголовков необходимо реализовать метод onBuildHeaders() и вызвать в нем метод loadHeadersFromResource():
1 2 3 4 5 6 |
public class SettingsActivity extends PreferenceActivity { @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); } } |
Когда пользователь выбирает пункт списка настроек, система открывает ассоциированный с ним фрагмент PreferenceFragment.
Примечание: при использовании заголовков, ваш подкласс PreferenceActivity не нуждается в методе onCreate(), поскольку выполняет только задачу загрузки заголовков.
Использование заголовков в старых версиях Android
Если ваше приложение поддерживает Android 3.0 и старше, необходимо создать дополнительный XML файл настроек, который использует основные элементы, работающие на старых версиях.
Вместо открытия нового экрана PreferenceScreen, каждый элемент <Preference> посылает намерение к PreferenceActivity, в котором указывается какой XML файл загрузить.
Например, создадим файл заголовков для Android 3.0 и выше (res/xml/preference_headers.xml
):
1 2 3 4 5 6 7 8 9 10 |
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.example.prefs.SettingsFragmentOne" android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" /> <header android:fragment="com.example.prefs.SettingsFragmentTwo" android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" /> </preference-headers> |
И здесь же создадим файл настроек с такими же заголовками, но для Android старше версии 3.0 (res/xml/preference_headers_legacy.xml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <Preference android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" > <intent android:targetPackage="com.example.prefs" android:targetClass="com.example.prefs.SettingsActivity" android:action="com.example.prefs.PREFS_ONE" /> </Preference> <Preference android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" > <intent android:targetPackage="com.example.prefs" android:targetClass="com.example.prefs.SettingsActivity" android:action="com.example.prefs.PREFS_TWO" /> </Preference> </PreferenceScreen> |
Поскольку поддержка <preference-headers>
была добавлена в Android 3.0, система будет вызывать метод onBuildHeaders() только, когда приложение запущено на Android 3.0 или выше. Чтобы загрузить “устаревший” файл (res/xml/preference_headers_legacy.xml
), необходимо проверить версию Android и вызвать addPreferenceFromResource():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // Загрузка устаревшей версии файла addPreferencesFromResource(R.xml.preference_headers_legacy); } } // Вызывается только на версии Honeycomb и выше @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); } |
Единственное, что осталось сделать, это обработать намерение, переданное в явление, чтобы определить какой файл настроек загружать. Извлечем из намерения действие и сравним его со списком известных действий, которые мы использовали в тегах <intent>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE"; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String action = getIntent().getAction(); if (action != null && action.equals(ACTION_PREFS_ONE)) { addPreferencesFromResource(R.xml.preferences); } ... else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // Загружаем устаревший файл настроек addPreferencesFromResource(R.xml.preference_headers_legacy); } } |
Помните, что последовательные вызовы метода addPreferencesFromResource() складывают все настройки в единый список, поэтому убедитесь, что вызываете его только один раз.
Чтение настроек
По умолчанию все настройки сохраняются в файле, который может быть получен из любой части приложения с помощью статического метода PreferenceManager.getDefaultSharedPreferences(). Метод возвращает объект SharedPreference, содержащий все пары ключ-значение, ассоциированные с объектами Preference, которые используются в PreferenceActivity.
Например, так можно получить значение одного из свойств из любого места программы:
1 2 |
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, ""); |
Отслеживание изменений настроек
Есть несколько причин для отслеживания изменений настроек. Чтобы получить обратный вызов при изменении одной из настроек, реализуйте интерфейс SharedPreference.OnSharedPreferenceChangeListener и зарегистрируйте слушателя для объекта SharedPreferences с помощью метода registerOnSharedPreferenceChangeListener().
Интерфейс имеет только один метод обратного вызова onSharedPreferenceChanged(), поэтому проще всего реализовать интерфейс как часть явления. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType"; ... public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals(KEY_PREF_SYNC_CONN)) { Preference connectionPref = findPreference(key); connectionPref.setSummary(sharedPreferences.getString(key, "")); } } } |
В приведенном примере, метод проверяет изменилось ли значение настройки с указанным ключом. Он вызывает метод findPrefence() для получения измененного объекта Preference. Если меняется настройка типа ListPreference или другого типа с множественным выбором, используется метод setSummary(), чтобы отобразить текущий статус настройки (смотрите опцию Sleep на рисунке 5).
Для правильного управления жизненным циклом явления, мы рекомендуем регистрировать и отменять регистрацию слушателя SharedPreferences.OnSharedPreferenceChangeListener при вызове методов onResume() и onPause() соответственно:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override protected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); } |
Внимание: когда вы вызываете registerOnSharedPreferenceChangeListener(), в данный момент менеджер свойств не хранит сильную ссылку на слушателя. Вы должны сохранить сильную ссылку на слушателя, чтобы он не был уничтожен сборщиком мусора. Мы рекомендуем хранить ссылку до тех пор, пока слушатель вам нужен.
В следующем примере вызывающий объект не хранит ссылку на слушателя, поэтому слушатель может быть уничтожен сборщиком мусора в произвольный момент времени:
1 2 3 4 5 6 7 |
prefs.registerOnSharedPreferenceChangeListener( // Плохо! Плохо! Плохо! new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // реализация слушателя } }); |
Никогда так не делайте! Вместо этого используйте подобный код:
1 2 3 4 5 6 7 |
SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // реализация слушателя } }; prefs.registerOnSharedPreferenceChangeListener(listener); |
Управление использованием сети
Начиная с Android 4.0, системное приложение “Настройки” позволяет пользователям увидеть как много данных их приложения загрузили из сети. Пользователь может запретить загрузку данных в фоновом режиме для любого приложения. Чтобы не давать повода отключить ваше приложение от сети, вы должны использовать ее эффективно, а также дать возможность пользователям указать в настройках, какие данные можно загружать для вашего приложения.
Например, вы можете разрешить задавать интервал синхронизации приложения, разрешить загрузку только при использовании WiFi, управлять загрузкой в роуминге и.т.п. Пользователь охотнее оставит ваше приложение в сети, если сможет контролировать все эти параметры.
После добавления необходимых настроек для контроля данных в PreferenceActivity, необходимо добавить фильтр намерения для действия ACTION_MANAGE_NETWORK_USAGE в файл манифеста. Например:
1 2 3 4 5 6 |
<activity android:name="SettingsActivity" ... > <intent-filter> <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> |
Фильтр показывает системе, что явление контролирует использование данных. Таким образом, когда пользователь проверяет в “Настройках”, сколько трафика используется, при нажатии на пункт вашего приложения запустится PreferenceActivity.
Создание собственных компонентов настройки
Android включает разнообразные подклассы Preference, позволяющие создавать интерфейс для различных типов настроек. Однако, вы можете не обнаружить компонент, который вам требуется, например нет стандартного компонента для настройки даты. В таких случаях необходимо создать собственный компонент настройки, расширив класс Preference или один из его подклассов.
При создании подкласса Preference, необходимо сделать следующее:
- Создать интерфейс, который отображается при выборе настройки.
- Вовремя сохранять значение настройки.
- При отображении объекта Preference задать текущее значение или значение по умолчанию.
- Предоставить значение по умолчанию при запросе системы.
- Если вы создаете собственный интерфейс для настройки, необходимо сохранять и восстанавливать его состояние при изменении состояния приложения или устройства (например при повороте экрана).
Создание пользовательского интерфейса
Если вы напрямую расширяете класс Preference, необходимо реализовать метод onClick(), в котором задаются действия, выполняемые при выборе настройки из списка. Однако, большинство собственных компонентов наследуются от класса DialogPreference, для отображения диалога, который упрощает процедуру. При наследовании от класса DialogPreference, необходимо в конструкторе класса вызвать метод setDialogLayoutResource(), чтобы указать разметку диалога.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class NumberPickerPreference extends DialogPreference { public NumberPickerPreference(Context context, AttributeSet attrs) { super(context, attrs); setDialogLayoutResource(R.layout.numberpicker_dialog); setPositiveButtonText(android.R.string.ok); setNegativeButtonText(android.R.string.cancel); setDialogIcon(null); } ... } |
Сохранение значения настройки
Вы можете сохранить значение настройки в любое время с помощью одного из методов persist....()
класса Preference, например persistInt() для целочисленного значения или persistBoolean() для логического.
Момент сохранения настроек зависит от базового класса, который вы выбрали для наследования. Если вы наследуете класс от DialogPreference, настройки должны сохраняться только при закрытии диалога с положительным результатом (то есть при нажатии ОК).
При закрытии DialogPreference система вызывает метод onDialogClosed(). Метод принимает аргумент, который указывает с каким результатом закрылся диалог, true
если с положительным (в таком случае необходимо сохранить настройки):
1 2 3 4 5 6 7 |
@Override protected void onDialogClosed(boolean positiveResult) { // При нажатии "OK", сохраняем новое значение if (positiveResult) { persistInt(mNewValue); } } |
В приведенном примере, mNewValue
это член класса, в котором хранится текущее значение опции. Вызов метода persistInt() сохраняет это значение в файл SharedPreferences, используя ключ, указанный в XML файле для данного элемента.
Установка текущего значения
Когда система добавляет на экран объект Preference, вызывается метод onSetInitialValue(), принимающий логический аргумент restorePersistedValue
, который показывает наличие сохраненного значения для опции. Если аргумент равен true
, вы должны получить сохраненное значение с помощью одного из методов getPersisted.....()
класса Preference, например с помощью метода getPersistedInt() для целочисленных значений. Как правило это значение используется для обновления пользовательского интерфейса, чтобы отобразить ранее заданное значение.
Если аргумент restorePersistedValue
имеет значение false
, вы должны использовать значение по умолчанию, которое передается вторым аргументом:
1 2 3 4 5 6 7 8 9 10 11 |
@Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { if (restorePersistedValue) { // Восстанавливаем текущее значение mCurrentValue = this.getPersistedInt(DEFAULT_VALUE); } else { // Устанавливаем значение по умолчанию, заданное в XML mCurrentValue = (Integer) defaultValue; persistInt(mCurrentValue); } } |
Каждый метод getPersisted.....()
принимает аргумент, который определяет значение по умолчанию для использования в тех случаях, когда на самом деле нет сохраненного значения или ключ не существует. В примере выше, локальная константа используется как значение по умолчанию в случае, если метод getPersistedInt() не сможет вернуть сохраненное значение.
Внимание: вы не можете использовать defaultValue
в качестве значения по умолчанию в методе getPersisted.....()
, поскольку это значение всегда равно null при restorePersistedValue=true
.
Предоставление значения по умолчанию
Если экземпляр вашего подкласса Preference определяет значение по умолчанию (с помощью атрибута android:defaultValue
), система вызывает метод onGetDefaultValue() при создании объекта, чтобы получить это значение. Вы должны реализовать данный метод, чтобы система сохранила значение по умолчанию в объекте SharedPreferences. Например:
1 2 3 4 |
@Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_VALUE); } |
Аргументы метода предоставляют все, что нужно: массив атрибутов и указатель позиции android:defaultValue
, который вы должны получить. Вы обязательно должны реализовать этот метод, чтобы указать локальное значение по умолчанию для атрибута, в случае если оно не определено.
Сохранение и восстановление состояния настроек
Так же, как это происходит с визуальными компонентами разметки типа View, подкласс Preference должен реагировать на перезапуск явления или фрагмента (например при повороте экрана), чтобы сохранять и восстанавливать свое состояние. Для этого используются методы обратного вызова onSaveInstanceState() и onRestoreInstanceState().
Состояние Preference определяет объект, реализующий интерфейс Parcelable. В качестве отправной точки для определения состояния объекта Android предоставляет класс Preference.BaseSavedState.
Чтобы определить как ваш подкласс Preference сохраняет состояние, вы должны расширить класс Preference.BaseSavedState. Вам нужно переопределить несколько методов и создать объект CREATOR.
Для большинства приложений, вы можете скопировать приведенный ниже код, просто поменяв строки, обрабатывающие value
, если ваш подкласс Preference сохраняет данные не целочисленного типа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private static class SavedState extends BaseSavedState { // В переменной хранится значение опции // измените при необходимости тип переменной int value; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); // Получаем текущее значение настройки value = source.readInt(); // Измените метод, если используется другой тип } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); // Записываем значение dest.writeInt(value); // Измените метод, если используется другой тип } // Стандартный объект creator использует экземпляр текущего класса public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } |
Вместе с реализацией Preference.BaseSavedClass, приведенной выше, необходимо реализовать методы onSaveInstanceState() и onRestoreInstanceState() для подкласса Preference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
@Override protected Parcelable onSaveInstanceState() { final Parcelable superState = super.onSaveInstanceState(); // Проверяем, сохранено ли состояние Preference if (isPersistent()) { // Не нужно сохранять, состояние уже сохранено // используем состояние суперкласса return superState; } // Создаем экземпляр подкласса BaseSavedState final SavedState myState = new SavedState(superState); // Сохраняем значение состояния, используя переменную, хранящую // текущее значение настройки myState.value = mNewValue; return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { // Проверяем, сохранились мы в onSaveInstanceState или нет if (state == null || !state.getClass().equals(SavedState.class)) { // Не сохранились, используем метод суперкласса super.onRestoreInstanceState(state); return; } SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); mNumberPicker.setValue(myState.value); } |