Android 設定画面を簡単に作成する (ANDROID HACKS)
今のところ、自分はAndroid行き詰まったら、http://goosh.org/ で、how to use surfaceview? などと打ち込んで、stackoverflow の投稿に行き当たり、それで解決することが多いのですが、本書に一通り、ざっと目を通したところ、 HACKS の名の通り、「あの機能はどうやって実現するのだろう?」と気になったときに役に立ちそうなTIPSが満載となっています。
さっそく、「HACK #113 設定画面を簡単に作成する」 の力を借りる日がやってきました。
これをみてちゃっちゃと設定画面を実装しましょう。
上記の様な画面を簡単に作成できるはず。
・・・ だったんですが、
デフォルトで提供されている設定用のUIコンポーネントは、以下ぐらいらしく、上記例のような、SeekBar を利用する設定画面は、そこまで簡単には作成できないようで。。。できても良さそうなのに。。。
UI部品 | 内容 |
CheckBoxPreference | チェックボックス |
EditTextPreference | テキストボックス |
ListPreference | ラジオボタンのリスト |
RingtonePreference |
着信音リスト |
もう少し時間をかけて考える必要がありました。
結果、以下の様に、実装し、ほぼほぼ動いているので、手順をメモします。
基本的には、
- Preference クラスの作成
- Preference クラス用のレイアウトの作成
- PreferenceActivity 用 レイアウトの作成
- PreferenceActivity の作成
となります。
SeekBarPreference クラスの作成
まず、設定画面用のActivity である、PreferenceActivity にて使用する、UI部品である、Preference クラスを継承して、SeekBar を使った設定用部品を作成します。
レイアウトは、後で記述しますが、コンストラクタで XML(/res/layout/preference_widget_seekbar.xml) を読み込みます。
Log を出力したところ、以下の順でメソッドが呼ばれるようです。
- onGetDefaultValue デフォルト値読み込み
- onSetInitialValue 初期値を設定
- onBindView ビューとデータをバインド
package info.typea.shujiroid.core; import info.typea.shujiroid.free.R; import android.content.Context; import android.content.res.TypedArray; import android.preference.Preference; import android.util.AttributeSet; import android.view.View; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; /** * @author piroto */ public class SeekBarPreference extends Preference implements OnSeekBarChangeListener { private static final int MAX_PROGRESS = 100; private static final int DEFAULT_PROGRESS = 50; private int currentProgress; private int oldProgress; public SeekBarPreference(Context context, AttributeSet attrs) { super(context, attrs); setWidgetLayoutResource(R.layout.preference_widget_seekbar); } /* * Preference が 呼び出されるときにデフォルト値が読み込まれる必要がある * 異なる Preference 型は異なる 値型 は持つはずなので、サブクラスはそれにあわせた型を返す必要がある */ @Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_PROGRESS); } /* * Preference の初期値を設定する * restorePersistedValue が true の場合、Preference 値を、SharedPreference からレストアすべき * false の場合 Preference 値にデフォルト値をセット * (SharedPreference の shouldPersist() が true の場合、可能ならSharedPreferenceに値を格納) */ @Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { if (restorePersistedValue) { currentProgress = getPersistedInt(currentProgress); } else { currentProgress = (Integer) defaultValue; persistInt(currentProgress); } oldProgress = currentProgress; } /* * Preference のために、ビューとデータをバインドする * レイアウトからカスタムビューを参照しプロパティを設定するのに適する * スーパークラスの実装の呼び出しを確実に行うこと */ @Override protected void onBindView(View view) { final SeekBar seekbar = (SeekBar) view.findViewById(R.id.pref_seekbar); if (seekbar != null) { seekbar.setProgress(currentProgress); seekbar.setMax(MAX_PROGRESS); seekbar.setOnSeekBarChangeListener(this); } super.onBindView(view); } @Override public void onStartTrackingTouch(SeekBar seekbar) {} @Override public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {} @Override public void onStopTrackingTouch(SeekBar seekbar) { int progress = seekbar.getProgress(); /* ユーザーが設定変更を行った後(内部的な値を設定する前)に呼び出す。 */ currentProgress = (callChangeListener(progress))?progress:oldProgress; persistInt(currentProgress); oldProgress = currentProgress; } }
SeekBarPreference UI 部品のレイアウトを定義(/res/layout/preference_widget_seekbar.xml)
今回 SeekBar のみで構成しましたが、通常の画面を作るように複数のコンポーネントを組み合わせることも出来そうです。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" > <SeekBar android:id="@+id/pref_seekbar" android:layout_width="wrap_content" android:layout_height="wrap_content" xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="100sp"> </SeekBar> </LinearLayout>
PreferenceActivity のレイアウト&定義(/res/xml/preferences.xml) を作成
設定画面のレイアウト XML を作成します。通常の画面レイアウトとは、置き場所や書き方が異なります。このあたりの詳細は、上記 Android Hacks ―プロが教えるテクニック & ツール を確認ください。
以下は、上記例の設定用XMLとなります。
android:key の値にて、SharedPreference から 設定値を取得することができる様になります。
問題の、SeekBarPreference の利用法は、info.typea.shujiroid.core.SeekBarPreference 要素を参照。
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="@string/lbl_brush_pref_title"> <info.typea.shujiroid.core.SeekBarPreference android:key="calligraphy_diameter" android:title="@string/lbl_diameter_of_brush" android:summary="@string/lbl_diameter_of_brush_summary" android:defaultValue="50"/> <info.typea.shujiroid.core.SeekBarPreference android:key="calligraphy_smoothness" android:title="@string/lbl_smoothness_of_brush" android:summary="@string/lbl_smoothness_of_brush_summary" android:defaultValue="50"/> <info.typea.shujiroid.core.SeekBarPreference android:key="calligraphy_alpha" android:title="@string/lbl_alpha_of_brush" android:summary="@string/lbl_alpha_of_brush_summary" android:defaultValue="50"/> <CheckBoxPreference android:key="calligraphy_antialias" android:title="@string/lbl_antialias" android:summary="@string/lbl_antialias_summary" /> <ListPreference android:key="calligraphy_color" android:title="@string/lbl_brush_color" android:summary="@string/lbl_brush_color_summary" android:entries="@array/ary_colors" android:entryValues="@array/ary_color_values" /> <ListPreference android:key="calligraphy_background_color" android:title="@string/lbl_background_color" android:summary="@string/lbl_background_color_summary" android:entries="@array/ary_colors" android:entryValues="@array/ary_color_values" /> <CheckBoxPreference android:key="shujiview_vibrate" android:title="@string/lbl_vibrate" android:summary="@string/lbl_vibrate_summary" /> </PreferenceCategory> </PreferenceScreen>
設定画面(PreferenceActivity) の作成
res/xml/preferences.xml で定義した、Preference (設定項目のUIコンポーネント) から値を設定、取得するためのキーを、設定画面呼び出し元のActivity から参照できる様に定数化していますが、それ以外は何もしてません。PreferenceActivity を継承して自分のアプリケーション用の Activity を作成し、addPreferencesFromResource を利用して、設定ファイルを参照するだけです。
package info.typea.shujiroid.core; import info.typea.shujiroid.free.R; import android.os.Bundle; import android.preference.PreferenceActivity; public class ShujiPreferenceActivity extends PreferenceActivity { public static final String KEY_CALLIGRAPYHY_DIAMETER = "calligraphy_diameter"; public static final String KEY_CALLIGRAPYHY_SMOOTH = "calligraphy_smoothness"; public static final String KEY_CALLIGRAPYHY_ALPAH = "calligraphy_alpha"; public static final String KEY_CALLIGRAPYHY_ANTIALIAS = "calligraphy_antialias"; public static final String KEY_CALLIGRAPYHY_COLOR = "calligraphy_color"; public static final String KEY_CALLIGRAPYHY_BACKGROUNDCOLOR = "calligraphy_background_color"; public static final String KEY_SHUJIVIEW_VIBRATE = "shujiview_vibrate"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }
設定を利用する
これで、設定画面が完成しました。メインのアクティビティから利用してみます。
設定画面を呼び出す
任意の値(ここでは、REQUEST_CODE_PREFERENCS としました)を指定して、startActivityForResult を呼び出す。
private static final int REQUEST_CODE_PREFERENCES = 1; : startActivityForResult(new Intent(this, ShujiPreferenceActivity.class),REQUEST_CODE_PREFERENCES);
設定画面を閉じる
設定画面を閉じると、onActivityResult が呼ばれるので、これをオーバーライドします。
先ほど設定した任意のコードが否かを requestCode で判定して、同一なら、SharedPrerferences から、設定値を取得します。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_PREFERENCES) { SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); int diameter = pref.getInt(ShujiPreferenceActivity.KEY_CALLIGRAPYHY_DIAMETER, 50); int smooth = pref.getInt(ShujiPreferenceActivity.KEY_CALLIGRAPYHY_SMOOTH, 50); : } }
以上の手順で、カスタムPreference を利用した設定画面を作成することが出来ます。
最後の詰めを考えさせる作りになっているあたり、さすが ANDROID HACKS という気もします。
また一歩野望に近づいた!
思いっ切りだぶるアプリで恐縮ですが「指筆」の設定画面を作るのに大変役立ちました。ありがとうございます。
まだAndroidアプリの開発をはじめたばかりで、ソースリストを見よう見真似で参考にさせていただいて、何度もリトライしてみているのですが、
> int diameter=pref.getInt(ShujiPreferenceActivity.KEY_CALLIGRAPYHY_DIAMETER, 50);
この行で取得した値はスライダーの値ではなく、関数の最後のパラメータの50しか帰ってきません。
私が正しくプログラムを理解していないのかもしとは思うのですが、各関数の出力結果などをもとに、何行目の辺りが問題がありそうか教えていただけないでしょうか?
よろしくお願いいたします。
連投失礼します。
どうやら、
>protected void onBindView(View view)
が呼ばれていないようです。
原因は何かわかりますでしょうか?
Googleで何時間か調査していたのですがわかりませんでした。
何かわかりましたら、よろしくお願いいたします。
すみません。
単純な変数の書き間違いでした。
上のコメントは削除してしまってください。
サイト、とても参考になりました。
ありがとうございましたm(_ _)m