前回の続き

今回は設定画面の作成方法について説明し、PreferenceScreenやPreferenceFragmentの理解を深めます。

サンプル: https://github.com/stack3/AndroidPreferenceSamples

サンプルを起動して、PreferenceScreenを選択すると以下の様な画面が表示されます。

01

名前を選択した場合、以下のようにテキスト入力が表示されます。OKを押すと反映されます。

02

性別を選択した場合は、選択肢が表示され、どれかを選択すると、それが反映されます。

03

お気に入りを選択した時は、複数の項目を選択(チェック)でき、OKを押すとそれが反映されます。

04

プッシュ通知はタップする度にON/OFFが切り替わります。

05

ニュースレターを受け取るはタップする度にチェックマークのON/OFFが切り替わります。

06

Androidには、こういった設定画面の大部分をXMLで作成する仕組みがあります。この辺はiOSより楽なところだと思います。

PreferenceScreenのXML定義

サンプルのres/xml/preference_screen_sample.xmlは以下のようになっています。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
  
  <EditTextPreference
    android:key="username"
    android:maxLength="20"
    android:title="@string/username"
    android:dialogTitle="@string/username" />

  
  <ListPreference
    android:key="gender"
    android:title="@string/gender"
    android:summary="@string/choose_gender"
    android:entries="@array/gender_names"
    android:entryValues="@array/gender_ids"
    android:dialogTitle="@string/gender" />

  
  <MultiSelectListPreference
    android:key="favorite_colors"
    android:title="@string/favorite_colors"
    android:summary="@string/choose_favorite_colors"
    android:entries="@array/color_names"
    android:entryValues="@array/color_values"
    android:dialogTitle="@string/favorite_colors" />

  
  <SwitchPreference
    android:key="push_notification"
    android:title="@string/push_notification"
    android:summary="@string/push_notification_summary" />

  
  <CheckBoxPreference
    android:key="receive_newsletter"
    android:title="@string/receive_newsletter"
    android:summary="@string/receive_newsletter_summary" />
</PreferenceScreen>

これが設定画面のレイアウトとなります。従来のActivityのレイアウト作成とは作法が異なるので注意してください。ただ設定画面作成に特化されているので楽です。

Preference要素

PreferenceScreen要素の中に、用途に応じて以下のPreference要素を記述するすることで、画面上の設定項目の配置ができます

  • EditTextPreference: テキスト入力を促す設定。今回の場合はユーザー名です
  • ListPreference: 複数の項目から1つを選択する設定
  • MultiSelectListPreference: 複数の項目から0もしくは1以上を選択する設定
  • SwitchPreference: ON/OFFを選択する設定
  • CheckBoxPreference: チェックのON/OFFを選択する設定

各種Preferenceの属性

  • android:key: SharedPreferenceに保存するときの項目名。設定保存ファイルのXML上の要素名になるので、異なる項目と重複しないように注意
  • android:title: 画面に表示するタイトル
  • android:summary: タイトルの下に表示する文字列。現在の設定値もしくは設定項目の説明
  • android:dialogTitle: EditTextPreference、ListPreferenceなど入力ダイアログを表示する場合、そのダイアログのタイトル
  • android:entries: ListPreference、MultiSelectListPreferenceでの選択項目名(表示名)
  • android:entryValues: ListPreference、MultiSelectListPreferenceでの選択項目値。この値がSharedPreferenceに保存される
  • android:password: EditTextPreferenceでパスワード入力にしたい時trueにする
  • android:maxLength: EditTextPreferenceで文字数入力上限を設定

01-1

03-1

他にもありますが、これらがよく使われる基本的なものです。

ListPreference、MultiSelectListPreferenceの選択項目

android:entries、android:entrieValuesに選択項目の表示名と値を設定しますが、これらもXMLで記述します。res/values/arrays.xmlを見てください。


<resources>
  <string-array name="gender_names">
    <item>@string/secret</item>
    <item>@string/male</item>
    <item>@string/female</item>
  </string-array>

  <string-array name="gender_ids">
    <item>0</item>
    <item>1</item>
    <item>2</item>
  </string-array>
</resources>

このようにstring-arrayとして定義しています。nameがgender_namesのものは表示項目名、gender_idsは項目値(設定に保存される値)です。当然ながら両者は同じ要素数にする必要があります。gender_namesの方は、strings.xmlからの参照にしています。ローカライズも考慮して直接文字列は書かないほうがいいでしょう。

ちなみに、ここはstring-arrayでなくては機能しないようです。integer-arrayでは駄目です。

上で述べたようにListPreferenceのentries,entryValuesに指定すると、選択項目として表示され、選択した値が保存されます。

<ListPreference
〜〜中略〜〜
    android:entries="@array/gender_names"
    android:entryValues="@array/gender_ids"

お気に入りの色(favorite_colors)も同様にしてあります。

PreferenceScreenSampleActivity

設定画面自体はもちろんActivityです。今回はPreferenceScreenSampleActivityが設定画面となります。

public class PreferenceScreenSampleActivity extends Activity {

※ 以前はPreferenceActivityを継承していましたが、API Level 11以降はActivityを直接継承して実装するほうが良さそうです。PreferenceActivityのいくつかのメソッドはdeprecatedになりましたので。

PreferenceFragment

PreferenceFragmentを継承してCustomPreferenceFragmentというInner Classを宣言しています。

public static class CustomPreferenceFragment extends PreferenceFragment {

このクラスと先程のPreferenceScreenのXMLがリンクすると考えてください。実装が若干長いので順に説明します。

onCreateメソッドで、PreferenceScreenのXMLとリンクさせ、一部の項目のSummaryに現在の値を設定します。

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // PreferenceScreenのXMLとリンク
    addPreferencesFromResource(R.xml.preference_screen_sample);

    // ユーザー名のPreferenceオブジェクトを得て、Summaryに項目値を設定
    EditTextPreference usernamePref = (EditTextPreference)findPreference(PREF_KEY_USERNAME);
    usernamePref.setSummary(usernamePref.getText());

    // 性別のPreferenceオブジェクトを得て、Summaryに項目値を設定
    ListPreference genderPref = (ListPreference)findPreference(PREF_KEY_GENDER);
    genderPref.setSummary(genderPref.getEntry());

    // お気入りの色のPreferenceオブジェクトを得て、Summaryに項目値を設定
    MultiSelectListPreference favoriteColorsPref = (MultiSelectListPreference)findPreference(PREF_KEY_FAVORITED_COLORS);
    favoriteColorsPref.setSummary(multiSelectListSummary(PREF_KEY_FAVORITED_COLORS));
}

PreferenceFragment#findPreferenceで設定キー名(android:keyで指定したもの)を指定すると、対応するPreferenceオブジェクトが得られます。

Summaryに現在値を設定したほうが良いものは、EditTextPreference、ListPreference、MultiSelectListPreferenceです。これらは入力・選択用のダイアログが表示されるまで、現在値を視認できません。よってsummaryに値を入れます。ここもXMLで書けると便利だったのですが・・・

SwitchPreference、CheckBoxPreferenceは、現在値が視認できるからそのままでよいでしょう。静的な説明をXMLから設定してあります。

multiSelectListSummaryメソッドは、このActivityの自前のメソッドです。複数選択項目(MultiSelectListPreference)の設定キーを指定すると、現在選択した値を元に表示名をカンマ区切りで返します。

private String multiSelectListSummary(String prefKey) {
    // Preferenceオブジェクを得る
    MultiSelectListPreference pref = (MultiSelectListPreference)findPreference(prefKey);
    // summaryに入れるentriesの要素を格納する配列
    ArrayList summaryArray = new ArrayList();
    // すべての選択項目の表示名を得る
    CharSequence[] entries = pref.getEntries();
    // すべての選択項目の値を得る
    CharSequence[] entryValues = pref.getEntryValues();
    // 設定保存されている選択された値を得る
    // Setオブジェクトなのでfor文で取得するとentriesの順番どおりにならないので注意
    Set values = pref.getValues();
    //
    // entriesの順番に従って選択された値の表示名をsummaryArrayに入れる
    //
    for (CharSequence entryValue : entryValues) {
        if (values.contains(entryValue)) {
            int index = pref.findIndexOfValue(entryValue.toString());
            if (index >= 0) {
                summaryArray.add(entries[index]);
            }
        }
    }
    // カンマ区切りで返す
    return TextUtils.join(", ", summaryArray);
}

設定項目の値が変更された時、ふたたびsummaryに現在値を入れないといけません。よって、値が変更されたことを受け取るListenerが必要になります。

private OnSharedPreferenceChangeListener onPreferenceChangeListenter = new OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals(PREF_KEY_USERNAME)) {
            EditTextPreference pref = (EditTextPreference)findPreference(key);
            pref.setSummary(pref.getText());
        } else if (key.equals(PREF_KEY_GENDER)) {
            ListPreference pref = (ListPreference)findPreference(key);
            pref.setSummary(pref.getEntry());
        } else if (key.equals(PREF_KEY_FAVORITED_COLORS)) {
            MultiSelectListPreference pref = (MultiSelectListPreference)findPreference(PREF_KEY_FAVORITED_COLORS);
            pref.setSummary(multiSelectListSummary(PREF_KEY_FAVORITED_COLORS));
        }
    }
};

このListenerは、onResumeで登録して、onPauseで解除するのがお約束です。

@Override
public void onResume() {
    super.onResume();
    SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
    sharedPreferences.registerOnSharedPreferenceChangeListener(onPreferenceChangeListenter);
}

@Override
public void onPause() {
    super.onPause();
    SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
    sharedPreferences.unregisterOnSharedPreferenceChangeListener(onPreferenceChangeListenter);
}

このSummaryに現在値を入れる辺りもXMLで記述できても良い気がしますが・・・

onCreate

getFragmentManagerを経由してCustomPreferenceFragmentとひもづける事で設定画面のレイアウトがActivity上に反映されます。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    getFragmentManager().beginTransaction().replace(android.R.id.content,
            new CustomPreferenceFragment()).commit();
}

通常、Activity#setContentViewを使ってレイアウトのXMLを指定しますが、設定画面の時はそうではないことに注意です。

保存した設定の読み出し

サンプルを起動して、「Preference Screen(Read)」を選択すると、設定内容を読み出し表示する画面が表示されます。

10

読み出しは以下のようにしています。

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String username = prefs.getString(PreferenceScreenSampleActivity.PREF_KEY_USERNAME, ""); 
String genderIdString = prefs.getString(PreferenceScreenSampleActivity.PREF_KEY_GENDER, "0");
int genderId = Integer.parseInt(genderIdString);
Set favoriteColors = prefs.getStringSet(PreferenceScreenSampleActivity.PREF_KEY_FAVORITED_COLORS, null);
boolean isPushNotification = prefs.getBoolean(PreferenceScreenSampleActivity.PREF_KEY_PUSH_NOTIFICATION, false);
boolean isReceiveNewsletter = prefs.getBoolean(PreferenceScreenSampleActivity.PREF_KEY_RECEIVE_NEWSLETTER, false);

設定内容をどこかで参照したい場合、このように読み出すことになるでしょう。

その3へ続く