2015/05/10

[Android][Java]ArrayAdapterはアダプター

AndroidにはArrayAdapterというクラスがあります。
Adapterの実装の一つで、ListViewやSpinnerを使ったUIを実装する際には必須といえるクラスなので、触ったことがないという人は少ないと思います。
しかし、一方Adapterという非常に抽象的なネーミングだったり、文字列を表示するだけならカスタマイズの必要がなかったりして、サンプルコードをコピペしたまま良くわからずに使っているという人も多いのではないかと思います。
実は私もそうでした。
全容がわかっていなくても、表面上に出てくるところをいじっていると目前の目的が達成されるので、ついつい疑問を後回しにしていました。

このクラスを使っていると、不可解な現象に遭遇します。
前述のasListと同じく、追加ができる場合と例外が発生する場合があるのです。
個の現象について、仕様はなにも語っていません。
いろいろ整理していくと初期化の違いが影響しているらしいということが分かりました。

ArrayAdapterはいくつかのコンストラクタを持っています。

ArrayAdapter(Context context, int resource)// 1
ArrayAdapter(Context context, int resource, int textViewResourceId)// 2
ArrayAdapter(Context context, int resource, T[] objects)// 3
ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)// 4
ArrayAdapter(Context context, int resource, List<T> objects)// 5
ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)// 6

1,2は要素を初期化しないコンストラクタ、3,4は初期値を配列、5,6はリストでそれぞれ提供するものです
下はそれぞれを動作確認するためのコードです。

private void test(Context context){
    String[]labels = {"test1", "test2", "test3"};
    ArrayList<String>array = new ArrayList<String>();
    for(String label : labels){
        array.add(label);
    }

    ArrayAdapter<String> adapter = ArrayAdapter<String>(context, 0);
    adapter.add("test4");//7

    adapter = ArrayAdapter<String>(context, 0, labels);
    adapter.add("test4");// 8

    adapter = ArrayAdapter<String>(context, 0, Arrays.asList(labels));
    adapter.add("test4");// 9

    adapter = ArrayAdapter<String>(context, 0, array);
    adapter.add("test4");// 10
}

このコードを実行すると7,10は成功し、8,9は例外が発生します。
この振る舞いは仕様に特に記述されていません。
この違いがなぜ起こるのか、しばらく疑問でした。

実は答えは簡単でした。
ArrayAdapterは外部にあるデータソースをGUIが操作できるインターフェイスに適合(Adapt)させるアダプターなのです。
私の誤認識はArrayAdapterが実装形式として内部に配列(Array)を持っているアダプタ(Adapter)だと思い込んでいたところにありました。
言い訳を許してもらえるなら、ArrayAdapterでなくCollectionAdapterだったなら、この誤解はなかったでしょう。
ArrayAdapterがアダプトするのはArrayだけではありません。ArrayListだけでもなく、すべてのコレクションを対象にしているのですから。

もうひとつ混乱を引き起こしたのは1,2のコンストラクタです。
こちらはデータソースのオリジナルがない状態を想定したコンストラクタだと思われます。つまり以下のように書くことができます。

ArrayAdapter<String>(this, 0, 0, new ArrayList<String>());

親切ではありますが、Adapterとしては過剰サービスだったのではないでしょうか。

ArrayListener.add()はデータソースのadd()をデリゲートしています。ArrayAdapterがデータの変更タイミングを察知するために必要なデリゲーションです。
こちらを通さない変更は都度notifyDataSetChanged()で知らせてやる必要があるのです。
構造が分かればいろいろなことがクリアになります。
実装ではありません、構造です。

0 件のコメント:

コメントを投稿