Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Preferences Framework

CheckBoxPreference
EditTextPreference
ListPreference
SwitchPreference
RingtonePreference
PreferenceCategory
PreferenceScreen
Импорт системных настроек
Настройки с зависимостями
Отслеживание изменений в Общих настройках
Меняем фон экрана настройки
SeekBarDialogPreference
NumberPickerDialogPreference
Секретная настройка VolumePreference
PreferenceFragment

На данный момент фреймворк считается устаревшим. Теперь используется пакет из Jetpack (AndroidX). Хотя общий принцип остался, поменялись только названия пакетов и классов. Ссылка на новый материал в конце статьи.

Настройки могут быть скрытыми или открытыми. Например, мы можем подсчитывать, сколько раз пользователь запускал нашу программу и при сотом запуске подарить ему бонус. Счётчик запуска приложения мы храним в файле настроек, но пользователь ничего не знает об этом. Но бывает так, что нам необходимо дать пользователю настроить функциональность приложения под свои вкусы. Например, сделать настройку "Напоминать о дне рождения котика". Если пользователь установит флажок у этой настройки (пусть только попробует не установить), то программа напомнит ему в нужный день о необходимости купить тортик.

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

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

Для сохранения предпочтений используются различные классы:

У элементов кроме своих собственных атрибутов есть обязательные атрибуты:

  • android:key - ключ, по которому можно определять значение
  • android:title - краткий текст настройки
  • android:summary - длинное описание настройки. Отражается под кратким текстом более мелким шрифтом
  • android:defaultValue - значение по умолчанию, которое будет использовано, если ничего не было выбрано

CheckBoxPreference

В созданном файле устанавливаем настройки. Начнём с 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>

Мы определили две настройки для программы:

  1. включение и выключение Wi-Fi
  2. показ подсказок

В нашем случае мы выводим флажки (элемент 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);
}

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

CheckBoxPreference

Наверное, вы обратили внимание, что метод addPreferencesFromResource() был зачёркнут и помечен, как устаревший. В новых версиях Android следует использовать фрагмент PreferenceFragment, о котором поговорим в другой раз.

EditTextPreference

Мы научились сохранять настройки при помощи флажков. Существует также возможность сохранения настроек в текстовом виде при помощи EditTextPreference. Подобный способ настройки позволяет сохранять текст, вводимый пользователем. Давайте добавим возможность устанавливать размер шрифта для текста. Откроем снова файл settings.xml и добавим новый элемент EditTextPreference:


<EditTextPreference
    android:key="test_size"
    android:title="Размер шрифта"
    android:summary="Устанавливает новый размер шрифта"
    android:defaultValue="14"
    android:dialogTitle="Введите размер шрифта (от 10 до 32)" />

Для удобства я буду сразу выводить строковые значения без использования строковых ресурсов. В своих рабочих проектах вам нужно использовать ресурсы обязательно.

Запустите проект и вызовите окно настроек. Теперь у нас появилась опция установки размера шрифта. Если щёлкнуть на созданном элементе, то откроется новое диалоговое окно с текстовым полем ввода.

В следующем уроке мы разберём, как считывать установленные значения размера шрифта.

ListPreference

Также можно использовать списки для хранения настроек. Для этой цели используется диалоговое окно 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

Поняв общий принцип, вы можете сами разобраться с другими элементами настроек. Рассмотрим ещё один элемент - 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>

В результате получим:

SwitchPreference

В зависимости от выбранного варианта мы можем менять текст настройки через атрибуты summaryOn/summaryOff.

RingtonePreference

Рассмотрим работу с настройкой, связанной с установкой мелодии для звонка. Предпочтение <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);

Настройки

Настройки

PreferenceCategory

Если в приложении используется слишком много настроек разного типа, то можно сгруппировать их по категориям, чтобы улучшить внешний вид окна настроек. Для этого в 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>, которые будут отображаться в отдельном окне. Родительский экран <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);
		// TODO Auto-generated constructor stub
	}

	public CustomPreferenceCategory(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public CustomPreferenceCategory(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}
	
	  @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.

SeekBarDialogPreference

Допустим, мы хотим настраивать параметр с помощью компонента SeekBar в диалоговом окне. Нам нужно наследоваться от класса DialogPreference и написать свой код.

res/layouts/preference_seek_bar_dialog.xml

Подготовим разметку.


<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>

res/values/attrs.xml

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

SeekBarDialogPreference.java

Напишем новый класс.


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];
			}
		};
	}
}

res/xml/settings.xml

Добавляем в разметку экрана настройки новый компонент. Обратите внимание на третью строчку, которая позволяют задавать собственные атрибуты 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);
}

NumberPickerDialogPreference

По такому же принципу мы можем создать диалоговое окно настройки с использованием NumberPicker.

res/layouts/preference_number_picker_dialog.xml


<?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>

res/values/attrs.xml

Если вы будете использовать пример вместе с предыдущим примером 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>

NumberPickerDialogPreference.java

Если вы используете пример вместе с предыдущим и не меняли файл 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>

Секретная настройка VolumePreference

В 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>

Так это выглядит на экране.

PreferenceFragment

В старых версиях 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.

header_settings.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():

HeaderPrefsActivity.java


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-файла и разметки:

UpdatePreferenceFragment.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);
	}
}

preference_update.xml


<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();
		}
	}
}

Теперь пользователь сразу видит текущую настройку стиля шрифта. Удобно же.

ListPreference

Открываем окно настроек из системных настроек Передача данных

Если вы зайдёте в Настройки | Передача данных и выберите свою программу, то внизу увидите неактивированную кнопку Настройки приложения. Чтобы её активировать, пропишите в манифесте у свой активности, которая отвечает за настройки, специальный фильтр 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>

Дополнительное чтение

Обсуждение урока на форуме

AndroidX: Preference

Реклама