Android、Java、Web系、Linux、マラソン等の備忘録

2012/10/27

ArrayAdapterで作ったListViewの行の更新

1 件のコメント
ArrayAdapterを継承したクラスでListViewを作る場合、些細な事ですが忘れがちなのでメモしておきます。基本的には、行の更新と言えどListViewはsetAdapterメソッドを叩いてやれば、ListViewを更新できますが、画面に収まりきらないほどのリスト項目を持ち、リストをタップする事でリスト項目を更新するような場合は注意が必要です。

具体例で言いうと、下図のような商品名が並んでいるリストがあり、ショッピングカートのボタンをタップすると、商品名の括弧内の数字(個数)が増えるようなアプリがあったとします。この場合は、下から2番目の”味付けカワハギ”までは、setAdapterでリストを更新しても特に問題はないと思いますが、”冷凍シメサバ”より下に項目が続く場合についてサンプルソースを基に注意点を見ていきたいと思います。







今回のサンプルでは、Activityにactivity_array_adapter_test.xmlをsetContentViewさせます。このxmlにListViewウィジェットを配置します。リストの中身は、別途list_item.xmlを作成し、その中にTextViewとショッピングカートアイコンのImageButtonを配置し、ArrayAdapterを継承したクラス内でlist_item.xmlに値を詰めてListViewに格納する実装です。


で、ショッピングカートのImageButtonをタップすると、行を更新するというOnClickListenerを設けます。このClickイベントで行うのが以下の2点です。
  • ArrayAdapterに渡しているListの更新
  • Viewの更新
setAdapterの何が問題かと言うと、

”冷凍シメサバ”より下にある表示されていない項目の場合は、まず項目を見える位置までスクロールさせる必要がありますね。そこで、ImageButtonをタップするわけですが、このタイミングで、List内部の値を更新しsetAdapterするとListViewはりんごから表示するので、せっかくスクロールさせた項目が隠れてしまいます。これは不便ですね。また、上記2つの片方しかやらないと、反映されなかったり、スクロールのタイミングで表示が元に戻ったりします。

では、その注意を盛り込んだサンプルソースです。


package net.takaiwa.arrayadaptersample;

import android.os.Bundle;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MainActivity extends Activity {

    private List<Otsumami> list = null;
    private ListAdapter list_adapter = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_array_adapter_test);

        // xmlからおつまみ名の配列を取得
        String[] item_name = this.getResources().getStringArray(R.array.tsumami);
        this.list = new ArrayList<Otsumami>(item_name.length);
        for(int i=0;i<item_name.length;i++) {
            // おつまみクラスに格納
            Otsumami otsumami = new Otsumami();
            otsumami.setItem_name(item_name[i]);

            this.list.add(otsumami);
        }
        this.list_adapter = new ListAdapter(this, this.list);
        // 表示
        ((ListView)findViewById(R.id.listView1)).setAdapter(this.list_adapter);
    }

    // おつまみと個数を格納する用クラス
    private class Otsumami {
        private String item_name = null;
        private int count = 0;
        public String getItem_name() {
            return item_name;
        }
        public void setItem_name(String item_name) {
            this.item_name = item_name;
        }
        public int getCount() {
            return count;
        }
        public void addCount() {
            this.count = this.count + 1;
        }
    }

    private class ListAdapter extends ArrayAdapter<Otsumami> {

        private LayoutInflater mInflater;

        public ListAdapter(Context context, List<Otsumami> objects) {
            super(context, 0, objects);
            this.mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        // positionはfinalにしないと、onClick内で機能しない
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {

            final Otsumami otsumami = this.getItem(position);
            if(null == convertView) {
                // 行のレイアウトを取得(TextViewをImageButtonのレイアウト)
                convertView = this.mInflater.inflate(R.layout.list_item, null);
            }

            if(null != otsumami) {
                // おつまみ名と個数を表示
                ((TextView)convertView.findViewById(R.id.textView1)).setText(otsumami.getItem_name() + " (" + otsumami.getCount() + ")");

                ((ImageButton)convertView.findViewById(R.id.imageButton1)).setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {

                        // リストの中身を変更する
                        Otsumami add_otumami = list.get(position);
                        add_otumami.addCount();
                        list.set(position, add_otumami);

                        // 表示の更新のやり方1:リストビューに通知をかける
                        list_adapter.notifyDataSetChanged();

                        // 表示の更新やり方2:ビューを直接いじる
//                        View parent = (View) v.getParent();
//                        ((TextView)(parent.findViewById(R.id.textView1))).setText(add_otumami.getItem_name() + " (" + add_otumami.getCount() + ")");
                    }
                });
            }
            return convertView;
        }
    }
}

ざっくり上から解説すると、

onClickメソッド:xmlからString配列を読み込んでListAdapter経由でListViewに反映

Otsumamiクラス:商品名とその個数の定義

ListAdapter:ListViewの立役者であるArrayAdapterを継承しているクラス


先ほどのClickイベントの話は、ListAdapter内のonClickメソッド内です。

1.ArrayAdapterに渡しているListの更新

ListAdapterに渡すListはこのActivityのフィールドなのでonClick内でもアクセスできます。また、タップされた位置はgetViewメソッドのパラメータであるpositionですが、final修飾子を付ける事でこちらもonClick内で取得できます。これらで、タップされた商品の個数を増やすべく⇒addCount()、それをlistに反映⇒list.set(position, add_otumami) という流れです。

2.Viewの更新

次に、Viewの更新方法ですが、2種類載せています(片方はコメントアウト)。一つは、ArrayAdapterにnotifyDataSetChanged()で更新を促す方法です。もう一つはonClickに渡されたパラメータであるView vを直接更新する方法です。基本的には、上記1の内容を反映する意味でもnotifyDataSetChanged()を叩く方がいいんじゃないかと思います。シンプルですし。

最初はこのgetView内の挙動が分かり辛いため、細かい所でミスが出やすい気がします。一連のソースはGithubに上げてます。

・Github : takaiwa/ArrayAdapterSample

1 件のコメント :

  1. ListViewの部分更新方法がわかりませんでしたが、こちらの記載内容を参考になんとか解決できました。

    返信削除