Настройки

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

Если вы хотите добавить в приложение окно настроек, используйте класс 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>. Каждый дочерний элемент представляет отдельный пункт в списке настроек.

Например:

В данном примере элементы CheckBoxPreference и ListPreference имеют следующие атрибуты:

android:key

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

Атрибут не требуется только для объектов PreferenceCategory и PreferenceScreen, а также Intent или Fragment.

android:title
Заголовок опции, который виден пользователю.
android:defaultValue
Указывает начальное значение опции, который система устанавливает в файле SharedPreferences. Всегда указывайте начальное значение для всех опций!

Создание группы настроек

Рисунок 2. Группы настроек.

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

  • Заголовок группы
  • Отдельный экран

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

Заголовки групп

Чтобы разделить группы заголовками (как на рисунке 2), поместите каждую группу в объект PreferenceCategory:

Отдельный экран настроек

Для того, чтобы поместить группу настроек на отдельный экран (как показано на рисунке 3), расположите ее внутри объекта PreferenceScreen.

Рисунок 3. Отдельный экран для группы настроек.

Пример:

Использование намерений

В некоторых случаях вы можете при выборе пункта настроек открывать другое явление, например web-браузер. Чтобы вызвать намерение при выборе пункта настроек, добавьте дочерний элемент <intent> в соответствующий элемент <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:

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

Использование фрагментов для отображения настроек

Если вы пишете приложение для Android 3.0 и выше, вы должны использовать для отображения списка настроек класс PreferenceFragment. Вы можете добавить PreferenceFragment в любое явление, для этого не нужно использовать PreferenceActivity.

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

Ваша реализация подкласса PreferenceFragment может просто содержать метод onCreate() для загрузки настроек, используя addPreferencesFromResource():

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

Примечание: PreferenceFragment не содержит собственного объекта Context. Если вам нужен контекст, вы можете вызвать метод getActivity(), но только в случае, если фрагмент присоединен к явлению. Когда фрагмент не присоединен, метод getActivity() возвращает null.

Установка значений по умолчанию

Настройки, вероятно, влияют на работу приложения, поэтому важно при первом запуске приложения инициализировать файл SharedPreferences стандартными значениями для каждого объекта Preference.

Первым делом нужно указать значения по умолчанию для элементов XML файла, используя атрибут android:defaultValue. Значение может иметь любой тип, который подходит для соответствующего объекта Preference. Например:

Затем из метода onCreate() главного явления необходимо вызвать метод setDefaultValues():

Вызов метода внутри onCreate() гарантирует, что настройки по умолчанию инициализированы и могут быть прочитаны для определения поведения приложения.

Метод принимает три аргумента:

  • Context (контекст) приложения
  • Идентификатор XML ресурса с настройками, для которого устанавливаются настройки по умолчанию.
  • Флаг, показывающий, должны ли настройки по умолчанию устанавливаться единажды.

    Если false, система установит настройки по умолчанию только если данный метод никогда ранее не вызывался (то есть свойство KEY_HAS_SET_DEFAULT_VALUES в файле настроек имеет значение false).

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

Использование заголовков настроек

В отдельных случаях, вы можете использовать первый экран настроек только в качестве списка для перехода к другим экранам. При использовании Android 3.0 и выше используйте для этого новую функцию “заголовки”, вместо вложенных элементов PreferenceScreen.

Для использования заголовков необходимо:

  1. Поместить каждую группу в отдельный экземпляр PreferenceFragment. То есть каждая группа должны храниться в отдельном XML файле.
  2. Создать XML файл заголовков, в котором перечислены все группы настроек и описано, какие фрагменты содержит соответствующий пункт списка.
  3. Создать подкласс PreferenceActivity для отображения настроек.
  4. Реализовать метод обратного вызова onBuildHeader(), чтобы указать файл заголовков.

Большим преимуществом использования PreferenceActivity является автоматическое разделение разметки на две панели при запуске на больших экранах.

Если приложение поддерживает Android ниже 3.0, вы можете использовать PreferenceFragment для создания двух-панельного интерфейса на новых устройствах, в то же время поддерживая традиционную много-экранную архитектуру на старых. Об этом немного ниже.

Рисунок 4. две панели, при использовании заголовков.

Рисунок 5. использование заголовков на смартфонах. При выборе пункта настроек, открывается соответствующий фрагмент.

Создание файла заголовков

Каждая группа настроек в списке заголовков создается с помощью элемента <header> внутри родительского контейнера <preference-headers>. Например:

С помощью атрибута android:fragment каждый заголовок указывает на экземпляр PreferenceFragment, который должен открываться при выборе заголовка.

Элемент <extras> позволяет передать пары ключ-значение во фрагмент внутри объекта типа Bundle. Фрагмент может получить аргументы с помощью метода getArguments(). Вы можете использовать один и тот же подкласс PreferenceFragment для всех групп, передавая в качестве аргумента название XML файла настроек, который должен быть загружен во фрагмент.

Например, мы используем аргумент с ключем "settings":

Отображение заголовков

Для отображения заголовков необходимо реализовать метод onBuildHeaders() и вызвать в нем метод loadHeadersFromResource():

Когда пользователь выбирает пункт списка настроек, система открывает ассоциированный с ним фрагмент PreferenceFragment.

Примечание: при использовании заголовков, ваш подкласс PreferenceActivity не нуждается в методе onCreate(), поскольку выполняет только задачу загрузки заголовков.

Использование заголовков в старых версиях Android

Если ваше приложение поддерживает Android 3.0 и старше, необходимо создать дополнительный XML файл настроек, который использует основные элементы, работающие на старых версиях.

Вместо открытия нового экрана PreferenceScreen, каждый элемент <Preference> посылает намерение к PreferenceActivity, в котором указывается какой XML файл загрузить.

Например, создадим файл заголовков для Android 3.0 и выше (res/xml/preference_headers.xml):

И здесь же создадим файл настроек с такими же заголовками, но для Android старше версии 3.0 (res/xml/preference_headers_legacy.xml):

Поскольку поддержка <preference-headers> была добавлена в Android 3.0, система будет вызывать метод onBuildHeaders() только, когда приложение запущено на Android 3.0 или выше. Чтобы загрузить “устаревший” файл (res/xml/preference_headers_legacy.xml), необходимо проверить версию Android и вызвать addPreferenceFromResource():

Единственное, что осталось сделать, это обработать намерение, переданное в явление, чтобы определить какой файл настроек загружать. Извлечем из намерения действие и сравним его со списком известных действий, которые мы использовали в тегах <intent>:

Помните, что последовательные вызовы метода addPreferencesFromResource() складывают все настройки в единый список, поэтому убедитесь, что вызываете его только один раз.

Чтение настроек

По умолчанию все настройки сохраняются в файле, который может быть получен из любой части приложения с помощью статического метода PreferenceManager.getDefaultSharedPreferences(). Метод возвращает объект SharedPreference, содержащий все пары ключ-значение, ассоциированные с объектами Preference, которые используются в PreferenceActivity.

Например, так можно получить значение одного из свойств из любого места программы:

Отслеживание изменений настроек

Есть несколько причин для отслеживания изменений настроек. Чтобы получить обратный вызов при изменении одной из настроек, реализуйте интерфейс SharedPreference.OnSharedPreferenceChangeListener и зарегистрируйте слушателя для объекта SharedPreferences с помощью метода registerOnSharedPreferenceChangeListener().

Интерфейс имеет только один метод обратного вызова onSharedPreferenceChanged(), поэтому проще всего реализовать интерфейс как часть явления. Например:

В приведенном примере, метод проверяет изменилось ли значение настройки с указанным ключом. Он вызывает метод findPrefence() для получения измененного объекта Preference. Если меняется настройка типа ListPreference или другого типа с множественным выбором, используется метод setSummary(), чтобы отобразить текущий статус настройки (смотрите опцию Sleep на рисунке 5).

Для правильного управления жизненным циклом явления, мы рекомендуем регистрировать и отменять регистрацию слушателя SharedPreferences.OnSharedPreferenceChangeListener при вызове методов onResume() и onPause() соответственно:

Внимание: когда вы вызываете registerOnSharedPreferenceChangeListener(), в данный момент менеджер свойств не хранит сильную ссылку на слушателя. Вы должны сохранить сильную ссылку на слушателя, чтобы он не был уничтожен сборщиком мусора. Мы рекомендуем хранить ссылку до тех пор, пока слушатель вам нужен.

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

Никогда так не делайте! Вместо этого используйте подобный код:

Управление использованием сети

Начиная с Android 4.0, системное приложение “Настройки” позволяет пользователям увидеть как много данных их приложения загрузили из сети. Пользователь может запретить загрузку данных в фоновом режиме для любого приложения. Чтобы не давать повода отключить ваше приложение от сети, вы должны использовать ее эффективно, а также дать возможность пользователям указать в настройках, какие данные можно загружать для вашего приложения.

Например, вы можете разрешить задавать интервал синхронизации приложения, разрешить загрузку только при использовании WiFi, управлять загрузкой в роуминге и.т.п. Пользователь охотнее оставит ваше приложение в сети, если сможет контролировать все эти параметры.

После добавления необходимых настроек для контроля данных в PreferenceActivity, необходимо добавить фильтр намерения для действия ACTION_MANAGE_NETWORK_USAGE в файл манифеста. Например:

Фильтр показывает системе, что явление контролирует использование данных. Таким образом, когда пользователь проверяет в “Настройках”, сколько трафика используется, при нажатии на пункт вашего приложения запустится PreferenceActivity.

Создание собственных компонентов настройки

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

При создании подкласса Preference, необходимо сделать следующее:

  • Создать интерфейс, который отображается при выборе настройки.
  • Вовремя сохранять значение настройки.
  • При отображении объекта Preference задать текущее значение или значение по умолчанию.
  • Предоставить значение по умолчанию при запросе системы.
  • Если вы создаете собственный интерфейс для настройки, необходимо сохранять и восстанавливать его состояние при изменении состояния приложения или устройства (например при повороте экрана).

Создание пользовательского интерфейса

Если вы напрямую расширяете класс Preference, необходимо реализовать метод onClick(), в котором задаются действия, выполняемые при выборе настройки из списка. Однако, большинство собственных компонентов наследуются от класса DialogPreference, для отображения диалога, который упрощает процедуру. При наследовании от класса DialogPreference, необходимо в конструкторе класса вызвать метод setDialogLayoutResource(), чтобы указать разметку диалога.

Например:

Сохранение значения настройки

Вы можете сохранить значение настройки в любое время с помощью одного из методов persist....() класса Preference, например persistInt() для целочисленного значения или persistBoolean() для логического.

Момент сохранения настроек зависит от базового класса, который вы выбрали для наследования. Если вы наследуете класс от DialogPreference, настройки должны сохраняться только при закрытии диалога с положительным результатом (то есть при нажатии ОК).

При закрытии DialogPreference система вызывает метод onDialogClosed(). Метод принимает аргумент, который указывает с каким результатом закрылся диалог, true если с положительным (в таком случае необходимо сохранить настройки):

В приведенном примере, mNewValue это член класса, в котором хранится текущее значение опции. Вызов метода persistInt() сохраняет это значение в файл SharedPreferences, используя ключ, указанный в XML файле для данного элемента.

Установка текущего значения

Когда система добавляет на экран объект Preference, вызывается метод onSetInitialValue(), принимающий логический аргумент restorePersistedValue, который показывает наличие сохраненного значения для опции. Если аргумент равен true, вы должны получить сохраненное значение с помощью одного из методов getPersisted.....() класса Preference, например с помощью метода getPersistedInt() для целочисленных значений. Как правило это значение используется для обновления пользовательского интерфейса, чтобы отобразить ранее заданное значение.

Если аргумент restorePersistedValue имеет значение false, вы должны использовать значение по умолчанию, которое передается вторым аргументом:

Каждый метод getPersisted.....() принимает аргумент, который определяет значение по умолчанию для использования в тех случаях, когда на самом деле нет сохраненного значения или ключ не существует. В примере выше, локальная константа используется как значение по умолчанию в случае, если метод getPersistedInt() не сможет вернуть сохраненное значение.

Внимание: вы не можете использовать defaultValue в качестве значения по умолчанию в методе getPersisted.....(), поскольку это значение всегда равно null при restorePersistedValue=true.

Предоставление значения по умолчанию

Если экземпляр вашего подкласса Preference определяет значение по умолчанию (с помощью атрибута android:defaultValue), система вызывает метод onGetDefaultValue() при создании объекта, чтобы получить это значение. Вы должны реализовать данный метод, чтобы система сохранила значение по умолчанию в объекте SharedPreferences. Например:

Аргументы метода предоставляют все, что нужно: массив атрибутов и указатель позиции android:defaultValue, который вы должны получить. Вы обязательно должны реализовать этот метод, чтобы указать локальное значение по умолчанию для атрибута, в случае если оно не определено.

Сохранение и восстановление состояния настроек

Так же, как это происходит с визуальными компонентами разметки типа View, подкласс Preference должен реагировать на перезапуск явления или фрагмента (например при повороте экрана), чтобы сохранять и восстанавливать свое состояние. Для этого используются методы обратного вызова onSaveInstanceState() и onRestoreInstanceState().

Состояние Preference определяет объект, реализующий интерфейс Parcelable. В качестве отправной точки для определения состояния объекта Android предоставляет класс Preference.BaseSavedState.

Чтобы определить как ваш подкласс Preference сохраняет состояние, вы должны расширить класс Preference.BaseSavedState. Вам нужно переопределить несколько методов и создать объект CREATOR.

Для большинства приложений, вы можете скопировать приведенный ниже код, просто поменяв строки, обрабатывающие value, если ваш подкласс Preference сохраняет данные не целочисленного типа:

Вместе с реализацией Preference.BaseSavedClass, приведенной выше, необходимо реализовать методы onSaveInstanceState() и onRestoreInstanceState() для подкласса Preference:

Добавить комментарий