Android 設定画面を簡単に作成する (ANDROID HACKS)

 

今のところ、自分はAndroid行き詰まったら、http://goosh.org/ で、how to use surfaceview? などと打ち込んで、stackoverflow の投稿に行き当たり、それで解決することが多いのですが、本書に一通り、ざっと目を通したところ、 HACKS の名の通り、「あの機能はどうやって実現するのだろう?」と気になったときに役に立ちそうなTIPSが満載となっています。

さっそく、「HACK #113 設定画面を簡単に作成する」 の力を借りる日がやってきました。

これをみてちゃっちゃと設定画面を実装しましょう。

android_preferences01

上記の様な画面を簡単に作成できるはず。

・・・ だったんですが、

android_preferences02

デフォルトで提供されている設定用のUIコンポーネントは、以下ぐらいらしく、上記例のような、SeekBar を利用する設定画面は、そこまで簡単には作成できないようで。。。できても良さそうなのに。。。

UI部品 内容
CheckBoxPreference チェックボックス
EditTextPreference テキストボックス
ListPreference ラジオボタンのリスト

RingtonePreference

着信音リスト

 

もう少し時間をかけて考える必要がありました。

結果、以下の様に、実装し、ほぼほぼ動いているので、手順をメモします。

基本的には、

  1. Preference クラスの作成
  2. Preference クラス用のレイアウトの作成
  3. PreferenceActivity 用 レイアウトの作成
  4. PreferenceActivity の作成

となります。

 

SeekBarPreference クラスの作成

まず、設定画面用のActivity である、PreferenceActivity にて使用する、UI部品である、Preference クラスを継承して、SeekBar を使った設定用部品を作成します。

レイアウトは、後で記述しますが、コンストラクタで XML(/res/layout/preference_widget_seekbar.xml) を読み込みます。

Log を出力したところ、以下の順でメソッドが呼ばれるようです。

  1. onGetDefaultValue  デフォルト値読み込み
  2. onSetInitialValue 初期値を設定
  3. 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 という気もします。

また一歩野望に近づいた!