ListViewについて

台風の影響でJJUG CCC行けなかったです。飛行機は欠航だし・・・
さて、以前、ListViewの色の変え方という記事を書きましたが、当時はAndroidを触り始めという事もあり、読み返してみてこれは酷いと思ったので書き直してみます。今もまだまだ勉強中ですが。
http://d.hatena.ne.jp/isher/20090713
http://d.hatena.ne.jp/isher/20090714

サンプルアプリの作成

今回使うサンプルとして、ドメインと国名の一覧を出力する簡単なリストを作ってみます。

一応ソースも乗せておきます。


  1. Eclipseでプロジェクトの作成

  2. 作成されたmain.xmlのTextViewを消して、ListViewを配置します。

  3. res/layout/main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    <ListView
        android:id="@+id/ListView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />
    </LinearLayout>

  4. list_at.xmlの作成

  5. 新しいレイアウトファイルとして、list_at.xmlを作ります。これがリストの項目を表示するためのレイアウトになります。
    LinearLayoutにドメインを表示するためのTextViewと、国名を表示するためのTextViewを並べます。

    res/layout/list_at.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:orientation="vertical"
        >
    <TextView
        android:id="@+id/Domain"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="2dp"
        android:textSize="15sp"
        />
    <TextView
        android:id="@+id/Country"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="2dp"    
        android:textSize="20sp"    
        />
    </LinearLayout>

  6. Activityの編集

  7. 今回はSimpleAdapterを使う事にしたので、データ用のMapのListを作成して、レイアウトIDを合わせています。
    mapの初期化をダラダラ書くのがダルいなーと、良い方法を探していたら下の記事を見つけたので参考にさせて頂きました。
    http://d.hatena.ne.jp/dai4649/20090611

    public class ListTest extends Activity {
        static final List<HashMap<String, String>> countries = 
            Arrays.asList(
                    new HashMap<String, String>() { { put("domain", "cn"); put("country","中華人民共和国"); } },
                    new HashMap<String, String>() { { put("domain", "de"); put("country","ドイツ連邦共和国"); } },
                    new HashMap<String, String>() { { put("domain", "fr"); put("country","フランス共和国"); } },
                    new HashMap<String, String>() { { put("domain", "il"); put("country","イスラエル国"); } },
                    new HashMap<String, String>() { { put("domain", "in"); put("country","インド"); } },
                    new HashMap<String, String>() { { put("domain", "jp"); put("country","日本国"); } },
                    new HashMap<String, String>() { { put("domain", "kr"); put("country","大韓民国"); } },
                    new HashMap<String, String>() { { put("domain", "uk"); put("country","イギリス"); } },                
                    new HashMap<String, String>() { { put("domain", "us"); put("country","アメリカ合衆国"); } }                
            );    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            ListView list = (ListView)findViewById(R.id.ListView);
            SimpleAdapter adapter = new SimpleAdapter(this,countries, R.layout.list_at, 
                    new String[]{"domain", "country"},
                    new int[]{R.id.Domain, R.id.Country});
            list.setAdapter(adapter);
        }
    }
    


ここまではAndroidのサンプルなどで良く出てくるものと同じなので、特に問題無いと思います。

レイアウトの変更

このListViewの文字色や背景色を変更したいと思います。通常は黒色の背景に白色の文字、フォーカスされている場合はオレンジ色の背景に黒色の文字になると思います。このように状態によって変化を付けたい場合、selectorと言うxmlを作成することで実装出来ます。

背景の変更

文字と背景でファイルが別になるので、背景のselectorから始めます。どのようなxmlを書けば良いのかについては、http://developer.android.com/intl/ja/reference/android/content/res/ColorStateList.htmlが参考になりますが、デフォルトのListViewでどう使われているか見てみます。Androidのソースの、frameworks/base/core/res/res/drawable/list_selector_background.xmlがそれらしい物でした。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false"
        android:drawable="@color/transparent" />
    <item android:state_focused="true" android:state_enabled="false"
        android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_enabled="false"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />
    <item android:state_focused="false" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_transition" />
    <item android:state_focused="true"
        android:drawable="@drawable/list_selector_background_focus" />
</selector>

どういうステータスの時(フォーカスが当たっている時や押されている時など)に、どういう表示をするのかをitemタグに書いています。例えば一番下の

 <item android:state_focused="true"
        android:drawable="@drawable/list_selector_background_focus" />

は、フォーカスが当たっている時にlist_selector_background_focusを使うということです。これは、list_selector_background_focus.9.pngというファイルが用意されてました。9-patchという、拡張しても崩れない画像ファイルです。http://developer.android.com/intl/ja/guide/developing/tools/draw9patch.html

ちなみにこんなファイルでした。ListViewをそのまま使うとこれになりますよね。


それでは、このファイルを参考に、オリジナルのselectorを作ってみる事にします。作成する場所についてはどこが適切なのか分からないのですが、とりあえず実験目的なのでres/xmlの下に作成しました。
res/xmlにlist_selector.xmlというファイル名で、新規xmlファイルを作成して、下のように記述しました。

res/xml/list_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" >
        <color android:color="#00000000" />
    </item>
    <item android:state_focused="true" android:state_enabled="false"
        android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_enabled="false"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_pressed" />
    <item android:state_focused="false" android:state_pressed="true"
        android:drawable="@drawable/list_selector_background_disabled" />
    <item android:state_focused="true"
        android:drawable="@drawable/list_selector_background_focus" />
</selector>

res/drawableに3つの画像を作っておきます。デフォルトのオレンジ色のを反転して青色にしてみました。灰色のdisabled用の画像はそのままです。
list_selector_background_disabled.9.png


list_selector_background_focus.9.png


list_selector_background_pressed.9.png


xmlではフォーカスがある時、ない時、押された時、無効化されてる時に上の画像を背景にする設定をしています。あとは長押しされた時などのステータスもあるようですが、今回は省略しています。
一番上の

    <item android:state_window_focused="false" >
        <color android:color="#00000000" />
    </item>

だけ違ってますが、これは透明色を設定しているだけです(#00000000は透明色)。つまり、フォーカスが無い場合は親のレイアウトの背景色で表示されます。(今回の場合はLinearLayoutのbackground)

これでselectorは完成なので、レイアウトファイルのmain.xmlを編集して、今回作ったselectorを使用するようにします。

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#FFC0C0"
    >
<ListView
    android:id="@+id/ListView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:listSelector="@xml/list_selector"
    android:cacheColorHint="#000000"
    />
</LinearLayout>

LinearLayoutにはandroid:backgroundを追加して背景色を変えています。先ほどのselectorにてListViewの背景に透明色を設定しているので、背景色は親であるLinearLayoutで設定するわけです。
ListViewにはandroid:listSelectorを追加して、先ほど作成したselectorを使用するようにしています。もう一つListViewにてandroid:cacheColorHintを設定していますが、これはスクロール時に表示される色を設定しています。詳しくはhttp://www.curious-creature.org/2008/12/22/why-is-my-list-black-an-android-optimization/

これでエミュレータを起動すると・・・

選択行が青色になりました。タッチパネルを押したり決定ボタンを押すと、濃い青色になるはずです。背景色は透明なので、LinearLayoutに設定した#FFC0C0のピンクっぽい色になっています。

文字色の変更

背景色を変更すると文字が見辛くなるので、こちらも変えてやらないといけません。方法はListViewの背景色の変更とほぼ同じで、selectorを作成します。まずはAndroidの規定のselectorを見てみます。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="false"
        android:color="@android:color/dim_foreground_dark_disabled"/>
    <item android:state_window_focused="false"
        android:color="@android:color/dim_foreground_dark"/>
    <item android:state_selected="true" android:state_enabled="false"
        android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
    <item android:state_pressed="true" android:state_enabled="false"
        android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
    <item android:state_selected="true"
        android:color="@android:color/dim_foreground_dark_inverse"/>
    <item android:state_pressed="true"
        android:color="@android:color/dim_foreground_dark_inverse"/>
    <item android:state_enabled="false"
        android:color="@android:color/dim_foreground_dark_disabled"/>
    <item android:color="@android:color/dim_foreground_dark"/>
</selector>

背景色の設定とほとんど同じです。背景色ではdrawableを使いましたが、textColorは基本的に単色なのでcolorを使っています。色の指定はres/valuesにcolors.xmlを作成して定数値を定義するのが良くやる方法なのですが、今回は実験なので色コードをべた書きで作ってしまいます。
res/xmlにtext_color_selector.xmlというファイル名で、新規xmlファイルを作成して、下のように記述します。

res/xml/text_color_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:state_enabled="false"
        android:state_pressed="true"
        android:color="#323232" />
    <item android:state_selected="true" android:state_enabled="false"
        android:color="#323232" />
    <item android:state_selected="true" android:state_pressed="true"
        android:color="#FFEEFF" />
    <item android:state_selected="false" android:state_pressed="true"
        android:color="#FFEEFF" />
    <item android:state_selected="true"
        android:color="#FFEEFF" />
    <item android:state_selected="false"
        android:color="#191919" />
</selector>

背景のと少し違ってますが、考え方は同じはずです。TextViewはフォーカスを得ないので、代わりにstate_selectedを使ってます。どういう時に、どのステータスがONになるのかがドキュメントを見る限り分からないので(Viewによっても違いそう)サンプル等を参考にしたり検索したりソース読んだりで作りました。選択されていない状態の時は黒っぽい色にして、選択されている場合は白っぽい色にしています。
ここで作ったselectorを、文字を出力しているTextViewに対して設定します。TextViewが配置されているのは、リストの項目用のレイアウトファイル、list_at.xmlです。

res/layout/list_at.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="60dp"
    android:orientation="vertical"
    >
<TextView
    android:id="@+id/Domain"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="2dp"
    android:textSize="15sp"
    android:textColor="@xml/text_color_selector"
    />
<TextView
    android:id="@+id/Country"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_margin="2dp"    
    android:textSize="20sp"
    android:textColor="@xml/text_color_selector"
    />
</LinearLayout>

追加してるのはTextViewのtextColorです。ここに今作成したselectorを設定して、状態によって色を変更します。これでエミュレータを立ち上げると・・

文字色も状態によって変えられるものが設定できました。

TextView一つ一つに設定するのも大変なので、こういう場合は「テーマ」を作ってActivity全体に反映させるのでしょうね。Androidが用意しているテーマを調べればもっと理解が深まりそうなので、次はそこを見て行きたいと思います。