13 April 2014

(Android) PreferenceメニューとPreferenceデータ保存

設定画面と、設定値のファイル保存が一体となっているAndroidのPreferenceの使い方メモ

Preferenceの保存先

Preferenceの保存は、/data/data/com.example.test_app/shared_prefs/○○○.xmlにXML形式で保存されている。なお、ファイラーなどで確認する場合は、ルート権限が必要。xmlは次のような内容となっている。

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="keyCheckboxPref" value="true" />
<string name="keyEditTextPref">test</string>
<string name="keyListPref">2</string>
<string name="ringtone_pref">content://settings/system/ringtone</string>
</map>

値をPreferenceに格納、読出する単純例

書き込みは次のように行う。書き込む変数の種類により、putBoolean, putInt, putFloat, putLong, putString, putStringSet などがあるので適宜使い分ける。

SharedPreferences pref = getSharedPreferences("SAMPLE_DIR_1", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString("TEST_ITEM_1", "テスト文字列");
editor.commit();

読み込みはこのような感じで

SharedPreferences pref = getSharedPreferences("SAMPLE_DIR_1", Context.MODE_PRIVATE);
String str = pref.getString("TEST_ITEM_1", "デフォルト値");

プリファレンス・データは、
/data/data/com.example.test_app/shared_prefs/SAMPLE_DIR_1.xmlに書き込まれる。

プリファレンス画面を表示する単純例(API 10以下も対応)

■ プリファレンス画面 XML の新規作成

Eclipseのパッケージ・エクスプローラまたはナビゲーターで、プロジェクトを選択した状態で、「ファイル」ー「新規」ー「その他」を選択して、「Android XMLファイル」の生成を選択する。

20140413-eclipse-pref-xml.jpg

「リソース・タイプ」を“Preference”とし、「ルート要素」は“PreferenceScreen”として、「完了」ボタンを押す。

開発環境で生成されたres/xmlファイルを開き、

20140413-eclipse-pref-xml2.jpg

作成したいリソース、今回はチェックボックスを追加し、“Attribute from Preference”カテゴリーの“Key”と“Title”を記述する。“Key”は実際にXMLに書き込まれる時のキー名になるので、他のプリファレンス画面と重複しないようにする。

このようにして完成したプリファレンスxmlファイルは次のようなものになる

res/xml/preference_test.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <CheckBoxPreference android:key="TEST_CHECKBOX" android:title="チェックボックス"/>
</PreferenceScreen>
■ プリファレンス画面のクラスの新規作成

次に、このプリファレンスxmlを実装するクラスを作成する。
Eclipseのパッケージ・エクスプローラまたはナビゲーターで、プロジェクトを選択した状態で、「ファイル」ー「新規」ー「クラス」の新規作成を行う。

20140413-eclipse-prefactivity.jpg

赤で囲った部分を設定していく。「スーパークラス」は“PreferenceActivity”クラスを指定する。クラスを作成したら、それを開いて、onCreate関数を作成する。パッケージ・エクスプローラまたはナビゲーターで、今回作成したプリファレンスのクラスを選択した状態で、「ソース」ー「メソッドのオーバーライド/実装」を選択する。

20140413-eclipse-prefactivity-oncreate.jpg

メソッド一覧の所で、キーボードから関数名を入力すると、インクリメントサーチが掛かる。onCreateのチェックボックスをONにして、OKボタンを押す。

最終的に、プリファレンスのクラスファイルは次のようになる。

PreferenceTest.java
package com.example.android_test_prefs;
 
import android.os.Bundle;
import android.preference.PreferenceActivity;
 
public class PreferenceTest extends PreferenceActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO 自動生成されたメソッド・スタブ
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preference_test);
    }
}

赤で着色した部分のみ、手入力している。ここで、プリファレンス画面設計のxmlを紐付けている。プリファレンス・データの保存のためのコーディング無いのは、これが全て自動で行われてしまうから。さすが、便利だ…

プリファレンス・データの実体ファイルは
/data/data/com.example.test_app/shared_prefs/com.example.test_app_preferences.xml
に保存される。

■ プリファレンス画面の呼び出しと、値の取り出しの実装 (MainActivity)

そして、MainActivityからプリファレンス画面を呼び出す処理などを追加すれば、サンプルコードの完成だ。

PreferenceTest.java
package com.example.android_test_prefs;
 
import android.support.v7.app.ActionBarActivity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
 
public class MainActivity extends ActionBarActivity {
 
    final int REQUEST_PREF_MENU = 0x1010;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(id == R.id.menu_pref_old){
            Intent intent = new Intent(this, PreferenceTest.class);
            startActivityForResult(intent, REQUEST_PREF_MENU);
            return true;
        }
       return super.onOptionsItemSelected(item);
    }
 
    @Override
    protected void onActivityResult(int arg0, int arg1, Intent arg2) {
        // TODO 自動生成されたメソッド・スタブ
        super.onActivityResult(arg0, arg1, arg2);
        if(arg0 == REQUEST_PREF_MENU){
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
            Boolean bool_checkbox = pref.getBoolean("TEST_CHECKBOX", false);
            if(bool_checkbox) Toast.makeText(MainActivity.this, "チェックボックスは ON", Toast.LENGTH_LONG).show();
            else  Toast.makeText(MainActivity.this, "チェックボックスは OFF", Toast.LENGTH_LONG).show();
        }
    }
 
}

緑で着色した部分はEclipseで自動生成した所、赤で書かれた部分がて入力した所。intentの受け取り側関数onActivityResultも、プリファレンスのonCreate関数作成と同じく、ほぼ自動生成に頼っている。

■ 新規作成したプリファレンス画面クラスのjavaファイルをプロジェクトに追加する

プロジェクトを実行する前に、作成したプリファレンス・クラスを読み込むよう、Manifestファイルに追加してやる。AndroidManifest.xmlを開いて、アプリケーション画面の一番下に追加位置がある。

20140413-eclipse-manifest-xml.jpg

実際のxmlファイルは次のようになる

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android_test_prefs"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="16" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="PreferenceTest"></activity>
    </application>
</manifest>

プリファレンス画面を表示する単純例(API 11以上の推奨例)

API 11位上ではPreferenceFragmentクラスを使うことが推奨されるため、Android 4以降でしか実行しないプログラムであれば、次のようにコーディングする。

■ プリファレンス画面のクラスの新規作成

Activity クラスでプリファレンス画面のクラスを新規作成し、実際のxmlはその内部クラスにPreferenceFragmentクラスを承継した新規クラスを作成し xml を紐付けるという、ひと手間掛かるめんどくささ…

上の例(API10版)のように、プリファレンス画面の新規クラスを作成し(但し、今回はActivityから派生する)、その後、下に示すような設定で「内部クラス」としてプリファレンス・フラグメントのクラスを作成する。

20140413-eclipse-preffragment.jpg

最終的に、プリファレンス画面のクラスは次のようになる。手入力したのは、赤で着色した部分のみ。

PreferenceTestNew.java
package com.example.android_test_prefs;
 
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.preference.PreferenceFragment;
 
@SuppressLint("NewApi")
public class PreferenceTestNew extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO 自動生成されたメソッド・スタブ
        super.onCreate(savedInstanceState);
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new PreferenceTestFragment())
                .commit();
    }
 
    public class PreferenceTestFragment extends PreferenceFragment {
 
        @Override
        public void onCreate(Bundle savedInstanceState) {
            // TODO 自動生成されたメソッド・スタブ
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.preference_test);
        }
    }
 
}
■ プリファレンス画面の呼び出しと、値の取り出しの実装 (MainActivity)

API10での例と同じだが、Android 2系で実行された場合の異常終了を避けるため、下の例のようにチェックを設けてもよい。

if(Build.VERSION.SDK_INT >= 11){
    Intent intent = new Intent(this, PreferenceTestNew.class);
    startActivityForResult(intent, REQUEST_PREF_MENU);
}

この部分以外はAPI10のサンプルコードと同じ。

PreferenceActivityやAlertDialogを使った画面例

20140413-prefmenu-mainscr.jpg
メイン画面(Preference現在値を表示)とメニュー表示

20140413-prefmenu.jpg
Preference設定画面

20140413-prefmenu-text.jpg
Preferenceテキスト入力ダイアログ

20140413-prefmenu-list.jpg
Preferenceリスト選択ダイアログ

XMLでPreference設定画面を定義し、表示する場合

■ プリファレンス画面の XML
res/xml/settings_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
 
    <CheckBoxPreference
        android:key="keyCheckboxPref"
        android:summary="現在の値:-"
        android:title="チェックボックス" />
 
    <EditTextPreference
        android:key="keyEditTextPref"
        android:summary="現在の値:-"
        android:title="テキスト入力"
        android:inputType="text"
        android:maxLength="10" />
        <!-- inputType=text : 改行禁止 -->
 
    <ListPreference
        android:key="keyListPref"
        android:entries="@array/listOptions"
        android:entryValues="@array/listValues"
        android:summary="現在の値:-"
        android:title="リスト選択" />
     
    <PreferenceCategory
        android:key="pref_category_2"
        android:title="第2階層の設定画面を表示" >
        <RingtonePreference
            android:key="keyRingtonePref"
            android:title="Ringtone"
            android:summary="現在の値:-"
            android:ringtoneType="ringtone" />
    </PreferenceCategory>
</PreferenceScreen>
■ リスト項目定義のXML
res/values/settings_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="listOptions">
        <item>選択肢1</item>
        <item>選択肢2</item>
        <item>選択肢3</item>
    </string-array>
    
    <string-array name="listValues">
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </string-array>
</resources>
■ プリファレンスう画面のクラス

Preferenceへの値の格納は、特にコードを記述しなくても自動で行われる。次のコーディングは、設定値を即時プリファレンス・メニュー画面上(summary)に反映するためのもので、それが必要無いのであれば、このクラス関数onCreateは自動作成された状態の「空」で構わない。

src/.../SettingsMenu.java
package com.example.android_settings_menu_1;
import android.os.Bundle;
import android.preference.*;
import android.preference.Preference.OnPreferenceChangeListener;
 
public class SettingsMenu extends PreferenceActivity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings_menu);
 
        // チェックボックスの現在値表示
        CheckBoxPreference cCheckBox1 = (CheckBoxPreference)findPreference("keyCheckboxPref");
        // 画面初期化時
        if(cCheckBox1.isChecked()){ cCheckBox1.setSummary("現在の値:ON"); }
        else{ cCheckBox1.setSummary("現在の値:OFF"); }
        // ユーザによる選択書き換え時
        cCheckBox1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                if(((Boolean)value).booleanValue()) {
                    pref.setSummary("現在の値:ON");
                }
                else {
                    pref.setSummary("現在の値:OFF");
                }
                return true;
            }
        });
 
        // テキストボックスの現在値表示
        EditTextPreference cEditText1 = (EditTextPreference)findPreference("keyEditTextPref");
        // 画面初期化時
        cEditText1.setSummary("現在の値:" + cEditText1.getText());
        // ユーザによる選択書き換え時
        cEditText1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                pref.setSummary("現在の値:" + value.toString());
                return true;
            }
        });
 
        // リスト選択の現在値表示
        ListPreference cList1 = (ListPreference)findPreference("keyListPref");
        // 画面初期化時
        cList1.setSummary("現在の値:" + cList1.getValue());
        // ユーザによる選択書き換え時
        cList1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                pref.setSummary("現在の値:" + value.toString());
                return true;
            }
        });
    }
}
■ プリファレンスの呼び出し・値の読み込みの実装 (MainActivity)
src/.../MainActivity.java
package com.example.android_settings_menu_1;
 
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
 
import android.widget.LinearLayout;
import android.widget.TextView;
import android.app.AlertDialog;
import android.widget.EditText;
import android.text.InputType;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.widget.Toast;
 
public class MainActivity extends ActionBarActivity {
 
    private int idText = 0x1001;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // 現在の設定値を表示するテキスト領域を確保する
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        setContentView(layout);
 
        TextView text = new TextView(this);
        text.setId(idText);
        layout.addView(text);
 
        // 画面に現在の設定値を表示する
        DisplayPrefOnScreen();
    }
 
    // 画面に現在の設定値を表示する
    private void DisplayPrefOnScreen() {
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
        String str;
        if(pref.getBoolean("keyCheckboxPref", false)) { str = "チェックボックス:ON\n"; }
        else { str = "チェックボックス:ON\n"; }
        str = str + "テキスト入力:" + pref.getString("keyEditTextPref", "") + "\n";
        str = str + "リスト選択:" + pref.getString("keyListPref", "---");
        TextView text = (TextView) this.findViewById(idText);
        text.setText(str);
        
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            // SettingsMenuクラスを呼び出して、プリファレンスメニューの初期化と表示を行う
            Intent intent = new Intent(this, SettingsMenu.class);
            // 設定完了時のコールバック
            startActivityForResult(intent, 10011);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == 10011){
            // 画面に現在の設定値を表示する
            DisplayPrefOnScreen();
        }
    }
}
■ 新規作成したプリファレンス画面クラスのjavaファイルをプロジェクトに追加する

src/.../SettingsMenu.javaを手動登録する必要がある。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android_settings_menu_1"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="10" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.android_settings_menu_1.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SettingsMenu" >
        </activity>
    </application>
 
</manifest>

プログラム中でPreferenceデータを読み書きする

メニューにPreferenceデータを読み書きする「テキスト入力」を追加する場合の説明

src/.../MainActivity.java
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            // SettingsMenuクラスを呼び出して、プリファレンスメニューの初期化と表示を行う
            Intent intent = new Intent(this, SettingsMenu.class);
            // 設定完了時のコールバック
            startActivityForResult(intent, 10011);
            return true;
        }
        else if (id == R.id.action_edittext) {
            // 現在の「テキスト入力値」をプリファレンスxmlより読みだす
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
            String str = pref.getString("keyEditTextPref", "");
 
            // テキスト入力AlertDialogを表示する
            final EditText editView = new EditText(MainActivity.this);
            editView.setLines(1);   // 1行
            editView.setInputType(InputType.TYPE_CLASS_TEXT);   // 改行を許可しない
            // AlertDialogを構築する
            AlertDialog.Builder dlg = new AlertDialog.Builder(this);
            dlg.setTitle("テキスト入力");
            dlg.setView(editView);
            editView.setText(str);
            dlg.setPositiveButton("OK", new DialogInterface.OnClickListener(){
                public void onClick(DialogInterface dialog, int which) {
                    // 入力された文字列をプリファレンスxmlに書き込む
                    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                    SharedPreferences.Editor edit = pref.edit();
                    edit.putString("keyEditTextPref", editView.getText().toString());
                    edit.commit();
                    // 画面に現在の設定値を表示する
                    DisplayPrefOnScreen();
                }
            });
            dlg.setNegativeButton("キャンセル", null);
            dlg.show();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

javaでPreference設定画面を定義し、表示する場合

src/.../SettingsMenu.java
package com.example.android_settings_menu_2;
 
import android.os.Bundle;
import android.preference.*;
import android.preference.Preference.OnPreferenceChangeListener;
 
public class SettingsMenu extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        PreferenceScreen pref = getPreferenceManager().createPreferenceScreen(this);
 
        // チェックボックスを作成
        CheckBoxPreference cCheckBox1 = new CheckBoxPreference(this);
        cCheckBox1.setKey("keyCheckboxPref");
        cCheckBox1.setTitle("チェックボックス");
        // メニューに追加
        pref.addPreference(cCheckBox1);
        // 画面初期化時
        if(cCheckBox1.isChecked()){ cCheckBox1.setSummary("現在の値:ON"); }
        else{ cCheckBox1.setSummary("現在の値:OFF"); }
        // ユーザによる選択書き換え時
        cCheckBox1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                if(((Boolean)value).booleanValue()) {
                    pref.setSummary("現在の値:ON");
                }
                else {
                    pref.setSummary("現在の値:OFF");
                }
                return true;
            }
        });
 
        // チェックボックスを作成
        EditTextPreference cEditText1 = new EditTextPreference(this);
        cEditText1.setKey("keyEditTextPref");
        cEditText1.setTitle("テキスト入力");
        cEditText1.setDialogTitle("テキスト入力");
        // メニューに追加
        pref.addPreference(cEditText1);
        // 画面初期化時
        cEditText1.setSummary("現在の値:" + cEditText1.getText());
        // ユーザによる選択書き換え時
        cEditText1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                pref.setSummary("現在の値:" + value.toString());
                return true;
            }
        });
 
        CharSequence[] arrayListTitle = new String[] { "選択肢1", "選択肢2", "選択肢3" };
        CharSequence[] arrayListValue = new String[] { "1", "2", "3" };
        // リストを作成
        ListPreference cList1 = new ListPreference(this);
        cList1.setKey("keyListPref");
        cList1.setTitle("リスト選択");
        cList1.setEntries(arrayListTitle);
        cList1.setEntryValues(arrayListValue);
        // メニューに追加
        pref.addPreference(cList1);
        // 画面初期化時
        cList1.setSummary("現在の値:" + cList1.getValue());
        // ユーザによる選択書き換え時
        cList1.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference pref, Object value) {
                pref.setSummary("現在の値:" + value.toString());
                return true;
            }
        });
 
        // プリファレンス メニューを画面表示する
        setPreferenceScreen(pref);
    }
}