Пишем свой Android Preference

Доброго времени суток. Если вы пишете приложения под Android, то наверняка сталкивались с настройками приложения (Preferences). На первый взгляд всё делается просто (см. сюда), но после беглого знакомства понимаешь что стандартных настроек очень мало. Их всего пять штук:
  • Preference
  • CheckBoxPreference
  • EditTextPreference
  • ListPreference
  • RingtonePreference

Очевидно что всех нужд эти настройки покрыть не могут.
Но тут самое время вспомнить что как то же можно настроить громкость звука или яркость экрана на телефоне — их нельзя настроить с помощью приведённого выше списка настроек! Идем в системные настройки и смотрим как это реализованно: Там есть обычная Preference, по клику на которую вылазит окошко с элементом управления «бегунок»(SeekBar). Впринципе приемлимое решение, но хочется избавится от лишнего окна и перенести бегунок прямо на экран настроек. Беглый поиск не нашёл приемлимых решений, и мы решили написать свой SeekBarPreference.
image

Наш SeekBarPreference должен обладать следущими качествами: он должен показовать текущее значение бегунка «на ходу», и мы хотим использовать его с разными параметрами длинны шага и количества шагов.

Собственно нам понадобится класс, которые отвечает за создание отображения настройки и поведение при перемещении ползунка.

public class SeekBarPreference extends Preference implements
 OnSeekBarChangeListener {

private TextView valueTextView;
 private int currentValue;
 private int max;

//конструктор, вытаскивает спецальные атрибуты настройки
 public SeekBarPreference(Context context, AttributeSet attrs) {
 super(context, attrs);
 max = attrs.getAttributeIntValue(
 "http://schemas.android.com/apk/res/com.sample", "max", 99);
 currentValue = attrs.getAttributeIntValue(
 "http://schemas.android.com/apk/res/com.sample",
 "currentValue", 0);
 }

//Переопределяем процедуру создания View для этой настройки
 @Override
 protected View onCreateView(ViewGroup parent) {

RelativeLayout layout = (RelativeLayout) LayoutInflater.from(
 getContext())
 .inflate(R.layout.seek_bar_preference_layout, null);

((TextView) layout.findViewById(R.id.title)).setText(getTitle());

SeekBar bar = (SeekBar) layout.findViewById(R.id.seekBar);
 bar.setMax(max);
 bar.setProgress(currentValue);
 bar.setOnSeekBarChangeListener(this);

valueTextView = (TextView) layout.findViewById(R.id.value);
 valueTextView.setText(currentValue+"");

return layout;
 }

//Функция, вызываемая каждый раз при перемещении ползунка
 public void onProgressChanged(SeekBar seekBar, int progress,
 boolean fromUser) {
 valueTextView.setText(progress+"");
 valueTextView.invalidate();
 }

public void onStartTrackingTouch(SeekBar seekBar) {
 }

//Функция, вызываемая после окончания движения пользователем
 public void onStopTrackingTouch(SeekBar seekBar) {
 currentValue = seekBar.getProgress();
 updatePreference(currentValue);
 notifyChanged();
 }

//Сохранение значения настройки
 private void updatePreference(int newValue) {
 SharedPreferences.Editor editor = getEditor();
 editor.putInt(getKey(), newValue);
 editor.commit();
 }
}


Для упрощния кода описание отображения было вынесено в отдельный layout:
<RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content">
 <TextView
 android:id="@+id/title"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textSize="20dp" />
 <TextView
 android:id="@+id/value"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_toRightOf="@+id/title"
 android:layout_alignBaseline="@+id/title"
 android:paddingLeft="5dp"
 android:textSize="18dp" />
 <SeekBar
 android:id="@+id/seekBar"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:paddingTop="5dp"
 android:paddingLeft="10dp"
 android:paddingRight="10dp"
 android:paddingBottom="3dp"
 android:layout_below="@+id/value" />


Layout содержит два текстовых поля — название настройки и текущее значение, и SeekBar.

Для переиспользования этой настройки нам надо определить особые атрибуты — максимальное значение ползунка и начальное положение ползунка. Самый простой способ — сделать специальные атрибуты для нашей настройки. Для этого создадим файл в папке «res/values» файл «attributes.xml»:
<declare-styleable name="SeekBarPreferenceAttrs">
 />
 />
 </declare-styleable>


Теперь у нас есть числовые атрибуты которые мы можем использовать при задании настройки:
<com.example.SeekBarPreference
 android:key="pref" 
 android:title="Sample preference" 
 sample:max="99"
 sample:currentValue="30" />


Но чтобы компилятор понял эти атрибуты надо определить из какого пакета их брать. Для этого в начале файла с настройками надо добавить определение пакета:
xmlns:sample="http://schemas.android.com/apk/res/com.example"


После этого можно насладится красивой настройкой без лишних окон.
image


0 комментариев

avatar
Отличная статья.
Но у меня есть предложения по улучшению:

1. В SDK предусмотрены стандартные атрибуты для SeekBar. Это max, progress и secondaryProgress. Чтобы ими воспользоваться в XML, куда подключаем этот элемент вместо sample:max достаточно написать android:max и т.д. Тогда в конструкторе можно заменить DNS на http://schemas.android.com/apk/res/android, а текущее значение прогресса назвать не currentValue, а progress.

2. После запуска наблюдается такой баг - настройка сохраняется, но при повторном открытии экрана настроек не восстанавливается ее значение. Чтобы полечить баг надо в методе onCreateView переделать код вот так:
...
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
int progress = settings.getInt(getKey(), currentValue);

SeekBar bar = (SeekBar) layout.findViewById(R.id.seekBar);
bar.setMax(max);
bar.setProgress(progress);
...

добавить переменную в класс:
private Context mContext;
и в конструкторе ее заполнить:
mContext = context;

3. Еще можно воспользоваться secondaryProgress для показа прошлого установленного значения, если в onCreateView добавить строку:
bar.setSecondaryProgress(progress);
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.