Androidで設定画面を作ろうと思い Google 先生に聞いたとおり PreferenceActivity を使ったら deprecated だって起こられた (;_;)

てか、Android は deprecated 多すぎ。 ググって出てきたコードの半分ぐらい引っかかる気がする。 しかも、代替手段とかすぐに分かるようになって無いし。

そんな訳でさらにググって PreferenceFragment を使わないといけない事が分かった。

以下、最小限のテンプレ。

public class SimplePrefActivity extends Activity {
    private SimplePrefFragment fragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fragment = new SimplePrefFragment();
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, fragment).commit();
    }

    public static class SimplePrefFragment extends PreferenceFragment
            implements OnSharedPreferenceChangeListener
    {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.my_pref); // => res/xml/my_pref.xml
        }

        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            // 変更通知処理
        }
    }
}
  • リソースは従来と同じでAndroid XMLファイルから Preference を選択して専用エディタで作る。
  • PreferenceFragment は static な内部クラスにしないといけないそうです。

リソース書いてみて分かったのだが記述できる内容がかなりしょぼく Checkbox,List選択,TextField しか使えない。

Radio とかは Preferenceクラスを拡張すればできるようだけど せっかく XML化して設定画面の UI を統一してるのにカスタマイズしてよいの?

さらに List選択,TextField の現在の設定値がタップしないと分からないようなっている。
流石にこれは有り得ないと思うので現在値を表示させる汎用のカスタマイズをして以下のコードに落ち着いた。

public class PrefActivity extends Activity {
    private PrefFragment fragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fragment = new PrefFragment();
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, fragment).commit();
    }
    public void setChanged(boolean b) {
        Intent intent = new Intent();
        // 設定変更があったことを呼び出し元に返す。
        setResult(b ? RESULT_OK : RESULT_CANCELED, intent);
    }

    public static class PrefFragment extends PreferenceFragment
            implements OnSharedPreferenceChangeListener
    {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.adkterm_pref); // res/xml/adkterm_pref.xml
        }

        @Override
        public void onResume() {
            super.onResume();
            resetSummary();
            getPreferenceScreen().getSharedPreferences()
                    .registerOnSharedPreferenceChangeListener(this);
        }

        @Override
        public void onPause() {
            super.onPause();
            getPreferenceScreen().getSharedPreferences()
                    .unregisterOnSharedPreferenceChangeListener(this);
        }

        @Override
        public void onSharedPreferenceChanged(SharedPreferences paramSharedPreferences, String paramString) {
            resetSummary();
            ((PrefActivity)getActivity()).setChanged(true);
        }

        // CheckBoxを除く項目のSummaryに現在値を設定する。
        public void resetSummary() {
            SharedPreferences sharedPrefs = getPreferenceManager().getSharedPreferences();
            PreferenceScreen screen = this.getPreferenceScreen();
            for (int i = 0; i < screen.getPreferenceCount(); i++) {
                Preference pref = screen.getPreference(i);
                if (pref instanceof CheckBoxPreference) continue;
                String key = pref.getKey();
                String val = sharedPrefs.getString(key, "");
                pref.setSummary(val);
            }
        }
    }
}

リソースXMLの例はこんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <ListPreference
        android:defaultValue="Landscape"
        android:entries="@array/direction_list"
        android:entryValues="@array/direction_list"
        android:key="orientation"
        android:title="Orientation" />
    <CheckBoxPreference
        android:defaultValue="true"
        android:key="keybord"
        android:selectable="true"
        android:title="Keybord" />
    <EditTextPreference
        android:defaultValue="16"
        android:dialogTitle="Font size (px)"
        android:key="fontsize"
        android:inputType="number"
        android:maxLength="2"
        android:title="Font size" />
    <EditTextPreference
        android:defaultValue="300"
        android:key="logsize"
        android:inputType="number"
        android:maxLength="3"
        android:title="Log size" android:dialogTitle="Log size (lines)"/>
</PreferenceScreen>

実行するとこうなります。(下段に小さく表示されるのが現在値)

設定参照側コードの例はこんな感じです。

public class Config  {
    public final static String K_KEYBORD = "keybord";
    public final static String K_ORIENT = "orientation";
    public final static String K_FONTSIZE = "fontsize";
    public final static String K_LOGSIZE = "logsize";
    public final static String V_LANDSCAPE = "Landscape";
    public final static String V_PORTRAIT = "Portrait";

    private static SharedPreferences sharedPrefs;

    public static void init(Context context) {
        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
    }

    public static Boolean getKeybord() {
        return sharedPrefs.getBoolean(K_KEYBORD, true);
    }
    public static String getOrientation() {
        return sharedPrefs.getString(K_ORIENT, V_LANDSCAPE);
    }
    public static int getFontsize() {
        return Integer.parseInt(sharedPrefs.getString(K_FONTSIZE, "16"));
    }
    public static int getLogsize() {
        return Integer.parseInt(sharedPrefs.getString(K_LOGSIZE, "300"));
    }
}
  • getする型を間違えると ClassCastException が起こります。
    • CheckBox のキーに getString() とかすると。
  • どのキーがどの型を返すのかは Preference のクラスをチェックするしかありません。
    • 上記の PrefFragment.resetSummary()参照

所感

設定内容をストレージに自動保存してくれたりするのはよいけど全体的に詰めが甘い感じ。
せめてラジオボタンとスライダーくらい標準で欲しい。

キー文字列の定義や設定値の参照クラスとかもXMLから自動生成して欲しい気がする。