スキップしてメイン コンテンツに移動

ArrayAdapterで作ったListViewの行の更新

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

    返信削除

コメントを投稿

このブログの人気の投稿

Javaでprivateなfieldやmethodにアクセスする

JUnitでテストしてると、privateなフィールドにアクセスして、値を参照したりセットしたりしたくなるわけですが、よく使うのでメモしておきます。 例えば、次のような対象のクラスがあるとします。 public class ParentClass { private String hoge = "ParentClass!!"; public void dispMsg() { System.out.println("dispMsg:" + hoge); } private void privateDispMsg(String msg) { System.out.println("dispMsg:" + msg); } }

GolangでWindows GUIアプリケーション

GUIアプリ作成の前提 社内ツールとしてexeで配布 開発環境はGoLandを使う 社内ツールとしてexeを配布ということであれば、Visual StudioでC#による開発だと思います。しかしながら、Go言語を習得したいのと、GoLandの補完機能が便利で、Android Studio使っていたこともあり、とっつきやすいという点からGo縛りでGUIアプリケーションを考えたいと思います。 lxn/walk Windows application library kit for Go. Windows向けしか考えていないので、まずこのライブラリなのですが、ボタンやコンボボックスが思ったように並ばなかったり、手軽にイメージボタンを配置したりなど、簡単にレイアウトを変更できない課題に直面しました。レイアウト作成だけで時間を取られてしまいます。そう言えば、Androidアプリ開発のときは、XMLでデザイン部分を切り離してたのを思い出して、今回の調査の運びとなりました。 fyne-io/fyne Cross platform native GUIs designed for Go based on Material Design. Supports: Linux, macOS, Windows, BSD, iOS and Android. walkと比べると、クロスプラットフォームで作成できるのですが、こちらもコードの中にデザインを書いていく形でした。 therecipe/qt therecipe/qt allows you to write Qt applications entirely in Go or JavaScript. Qt Creatorなるものがあるようで、デザイン部分を切り離せる印象はあるものの、最新のPCでもビルドに時間がかかるようで、学習コストもかかるという記事を見かけて断念しました。 どうしたものかとツイートしましたら、下記のようにご助言をいただいた。 同じ課題に対し色々試した結果、PWAのフルキャッシュドに落ち着いた。中身はGo+WASMで。 https://t.co/e60whDTV16 — のぼのぼ📡 (@nobonobo) July 21, 2020 PWA 早速調べてみました。Googleが進めているプロジェクトで、ネイティブアプリのよ

Eclipseの高速化メモ

Eclipseが重いと一言に言っても、いろいろな工程での話があると思いますが、過去記事のなども含めてこの記事にピックアップしておきたいと思います。以下はWindows環境での話です。