読者です 読者をやめる 読者になる 読者になる

mixi engineer blog

ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

続・Android開発のちょっとしたお話

Android Java mixi

こんにちは。横幕です。
今回もAndroid(TM)開発についてお話をしたいと思います。

設定画面の作り込み

今回のトピックは、設定画面のちょっとした工夫の仕方についてです。

Androidでは、PreferenceActivityという設定画面を作るためのActivityが用意されています。
個々の設定項目はXMLで記述し、それをPreferenceActivityがコントローラとして画面を制御するような形になります。

設定画面の大まかな作り方

まずは、どんな設定項目を準備するのかを、res/xml/pref.xmlに定義します。
Androidには予め幾つかの設定方法を用意してあり、例えば項目の一覧の中から1つ選択するListPreferenceや、チェックボックスの状態で設定を変更するCheckBoxPreferenceなどがあります。
また、設定項目のまとまりごとにカテゴライズするためのPreferenceCategoryも用意されており、これらを組み合わせて設定画面に表示するものを記述してきます。

* res/xml/pref.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory android:title="設定1">
        <CheckBoxPreference
            android:key="checkbox_1"
            android:title="チェックボックス1"
            android:summaryOn="チェックしました"
            android:summaryOff="チェックしていません"/>
        <ListPreference
            android:key="list_1"
            android:title="リスト1"
            android:summary="選択した項目"
            android:dialogTitle="選んでください"
            android:entries="@array/list_entries"
            android:entryValues="@array/list_entry_values"/>
    </PreferenceCategory>
    <PreferenceCategory android:title="設定2">
        <EditTextPreference
            android:key="edit_text_1"
            android:title="テキスト1"
            android:summary="入力した内容"
            android:dialogTitle="入力してください"/>
    </PreferenceCategory>
</PreferenceScreen>

* res/values/arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="list_entries">
        <item>First Element</item>
        <item>Second Element</item>
        <item>Third Element</item>
    </string-array>
    <string-array name="list_entry_values">
        <item>First Value</item>
        <item>Second Value</item>
        <item>Third Value</item>
    </string-array>
</resources>

設定項目のスタイルに応じて、記述するアトリビュートも変わってきますね。
android:keyは、設定を読み書きするために使う情報です。キーとバリューのペアで扱うイメージですね。
android:titleには、何を設定するのかを記述します。例えば、「文字の大きさ」と言った具合です。
android:summaryには、実際に何が設定されているかを記述します。「文字の大きさ」であれば、「14pt」などですね。
これ以外にも、ダイアログを表示するListPreferenceやEditTextPreferenceには、ダイアログのタイトルのアトリビュート(android:dialogTitle)や、
一覧の項目を設定するためのアトリビュート(android:entries、android:entryValues)などが用意されています。

これらのアトリビュートはプログラムで簡単に操作できるようになっています。

次にプログラム側の作り方を見ていきましょう。
設定画面は、PreferenceActivityを継承して作ります。
通常のActivityと違うのは、onCreateをオーバライドしたときに、setContentView()でレイアウトを指定するのではなく、addPreferencesFromResource()でpref.xmlを読み取ることです。

* MyPreferenceActivity.java

package com.text.app05;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class MyPreferenceActivity extends PreferenceActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.pref);
  }
}

* AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.app04"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="8" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".Example05Activity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:label="@string/app_name"
            android:name=".MyPreferenceActivity" >
        </activity>
    </application>
</manifest>

これだけで、設定画面を表示することができ、実際に設定内容の読み出し・保存も出来ているはずです。

画面1

しかし、このままでは設定内容に応じて見た目を変更することができません。
設定に変更があったことを知るには、OnPreferenceChangeListenerを実装する必要があります。

package com.test.app05;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;

public class MyPreferenceActivity extends PreferenceActivity {
    private ListPreference mListPreference;
    private EditTextPreference mEditTextPreference;
    private SharedPreferences mSharedPreference;
    private OnPreferenceChangeListener mListPreferenceListener = new OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            preference.setSummary((String) newValue);
            return true;
        }
    };
    private OnPreferenceChangeListener mEditTextPreferenceListener = new OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            preference.setSummary((String) newValue);
            return true;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.pref);

        mSharedPreference = PreferenceManager.getDefaultSharedPreferences(this);
        mListPreference = (ListPreference)findPreference("list_1");
        mEditTextPreference = (EditTextPreference)findPreference("edit_text_1");

        mListPreference.setSummary(mSharedPreference.getString("list_1", "First Value"));
        mListPreference.setOnPreferenceChangeListener(mListPreferenceListener);
        mEditTextPreference.setSummary(mSharedPreference.getString("edit_text_1", "Not assigned"));
        mEditTextPreference.setOnPreferenceChangeListener(mEditTextPreferenceListener);
    }
}

個々の設定項目(Preference)を取得するには、findPreference()を利用します。引数にandroid:keyアトリビュートの値を指定することで特定します。
そして、取得してきたPreferenceにOnPreferenceChangeListenerをセットします。
これで、設定を変更すると、それに応じて内容の表示も切り替えることができるようになりました。

画面2

表現の工夫

設定、と一口に言っても、実際の関心は、文字の大きさ・着信音・背景色・時間など、多岐にわたります。

そのような関心の中で、現在設定されている内容を表示することを考えてみると、
例えば、文字の大きさであれば、現在設定されている内容の表現として、「大」「中」「小」と言った表示の表現がありますし、
時間についても、「1時間ごと」「2時間ごと」等と言った表現が考えられます。

ではここで、色の設定について考えてみたいと思います。

今何色に設定しているか、を表示したい場合、色の名前を表示するという手段もありますが、
直感的には、色そのものを表示したほうが、分かりやすさがぐっと向上すると思います。

実装方法

設定画面の個々の項目には、widgetLayoutというアトリビュートがあります。
公式開発ドキュメントには、レイアウトへのリファレンスを記述することができることが書かれています。実際、チェックボックスを表示するCheckboxPreferenceもこのwidgetLayoutを利用して作られているようです。
つまり、このwidgetLayoutに独自のレイアウトのリファレンスを記述すれば、チェックボックス以外のViewを表示することができることになります。

例えば、先程のpref.xmlの、ListPreferenceにwidgetLayoutを利用してみると...

* pref.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory android:title="設定1">
        <CheckBoxPreference
            android:key="checkbox_1"
            android:title="チェックボックス1"
            android:summaryOn="チェックしました"
            android:summaryOff="チェックしていません"/>
        <ListPreference
            android:key="list_1"
            android:title="リスト1"
            android:summary="選択した項目"
            android:dialogTitle="選んでください"
            android:entries="@array/list_entries"
            android:entryValues="@array/list_entry_values"
            android:widgetLayout="@layout/indicator"/>
    </PreferenceCategory>
    <PreferenceCategory android:title="設定2">
        <EditTextPreference
            android:key="edit_text_1"
            android:title="テキスト1"
            android:summary="入力した内容"
            android:dialogTitle="入力してください"/>
    </PreferenceCategory>
</PreferenceScreen>

* indicator.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true">
    <View
        android:id="@+id/indicator"
        android:layout_width="30dip"
        android:layout_height="30dip"
        android:background="#FF0000"/>
</FrameLayout>

画像4

このように表示されるはずです。

レイアウトへのリファレンスですから、Viewに限らず、ImageViewを置いて画像を表示する、ということも実現可能ですね。

せっかくですから、項目に応じてViewの色を変更してみましょう。

widgetLayoutを操作するには、ListPreferenceを拡張する必要があります。
ListPreferenceを継承し、onBindView()の中で、widgetLayoutのViewを取り出して、背景色を変更します。

package com.test.app05;

import com.test.app05.R;
import android.content.Context;
import android.graphics.Color;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.view.View;

public class MyListPreference extends ListPreference {
    public MyListPreference(Context context) {
        super(context);
    }

    public MyListPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onBindView(View view) {
        super.onBindView(view);
        View v = view.findViewById(R.id.indicator);
        if ("First Value".equals(getValue())) {
            v.setBackgroundColor(Color.RED);
        } else if ("Second Value".equals(getValue())) {
            v.setBackgroundColor(Color.BLUE);
        } else if ("Third Value".equals(getValue())){
            v.setBackgroundColor(Color.GREEN);
        } else {
            v.setBackgroundColor(Color.RED);
        }
    }
}

また、pref.xmlのListPreferenceを、独自に定義したMyListPreferenceに変更します。
要素名をFQNで指定するだけで完了です。


<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory android:title="設定1">
        <CheckBoxPreference
            android:key="checkbox_1"
            android:title="チェックボックス1"
            android:summaryOn="チェックしました"
            android:summaryOff="チェックしていません"/>
        <com.test.app05.MyListPreference
            android:key="list_1"
            android:title="リスト1"
            android:summary="選択した項目"
            android:dialogTitle="選んでください"
            android:entries="@array/list_entries"
            android:entryValues="@array/list_entry_values"
            android:widgetLayout="@layout/indicator"/>
    </PreferenceCategory>
    <PreferenceCategory android:title="設定2">
        <EditTextPreference
            android:key="edit_text_1"
            android:title="テキスト1"
            android:summary="入力した内容"
            android:dialogTitle="入力してください"/>
    </PreferenceCategory>
</PreferenceScreen>

これで、設定の内容に応じてwidgetLayoutの背景色を変更することができるようになりました。

画像4

終わりに

今回は設定画面の作り込みについてのお話でした。
利用頻度はそれほど高くない場所かもしれませんが、だからこそ、使い方を学習する手間・思い出す手間が極力少ないUI設計にしていくことが重要になってくるのだと思います。

最後になりましたが、先日弊社主催のAndroid開発イベント「Build Challenge for Android」が開催されました。
その際に使用したスライド資料を掲載致しますので、こちらも合わせて参考にしていただければと思います。