うちなー えんじにあ ぶろぐ

開発に関するブログです 沖縄県民 Android好き

Android ListViewとAdapterとViewHolder

かなり重要なListView!!
なんでもかんでもListView!!!w
コンテンツの中で重要なポジションを担うと思っています。
ListViewの使い方!解説していきます。

ListViewの使い方

  • XML
  • ListViewとAdapter 概要
  • ListViewとAdapter設定方法
  • ListViewの注意点
    • DataBindingとViewHolderの組み合わせ

最終ゴール

f:id:kamiya-kizuku:20171226184004p:plain

環境

AndroidStudio 3
Java
DataBinding

XML

それでは早速、xmlどん!

<ListView
    android:id="@+id/listview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

特に設定いりません。
とりあえず、ListViewのっけましょう!

ListViewの他に、
RecycleViewというものや、
ツリー表示等が行える、ExpandableListViewというのもありますが、
これはまた次回に!
※2018/2/19 RecycleViewについて少し書きました
kamiya-kizuku.hatenablog.com

※2018/3/3 ExpandableListViewについて少し書きました
kamiya-kizuku.hatenablog.com

ListViewとAdapter 概要

ListViewとAdapter、
恐らく一番最初にAndroid開発する際につまずくポイントでは無いかと思っています。
※偏見かもですw

それでは簡単に説明していきます。
ListViewはなんとなくわかると思いますが、
Adapterってなんやねんってなるかと思います。

簡単に説明すると、
リストの1行に表示するレイアウトとデータと紐付けてくれる奴!
って感じです。
最終ゴールの奴をベースにお伝えすると、

レイアウト
画像とか、名前とか詳細情報を表示するためのTextやImageの部分
xmlとかで記述する感じ
※要はView

データ
表示する画像のURLとか、名前の文字列情報とか、詳細情報の文字列情報
とかそんなイメージです。

Adapterには何種類かあります!

BaseAdapter(今回説明はこれ、個人的には一番好き)

独自でカスタマイズする時に一番使いやすい?
と思っているAdapter

ArrayAdapter

配列の要素を持ったAdapter

SimpleAdapter

xmlをベースにIDとかデータを諸々指定するタイプのAdapter

今時点では、ListViewと色んな種類があるAdapterの一つを、
なんとかして紐付けて設定するんだな〜
程度の認識で良いかと!

Adapterの役割を図にするとこんな感じ
f:id:kamiya-kizuku:20171226194100p:plain

ListViewとAdapter設定方法

まずはAdapterから

とりあえずプログラムどん!!

public class SampleAdapter extends BaseAdapter {

    private List<SampleModel> mModels;

    /**
     * コンストラクタ
     * @param mModels List {@link SampleModel}
     */
    public SampleAdapter(List<SampleModel> mModels) {
        this.mModels = mModels;
    }

    /**
     * 設定必須
     * Listに表示するデータの個数を設定する
     * @return int
     */
    @Override
    public int getCount() {
        return mModels.size();
    }

    /**
     * 任意
     * @param position int
     * @return {@link SampleModel}
     */
    @Override
    public SampleModel getItem(int position) {
        return mModels.get(position);
    }

    /**
     * 任意
     * @param position int
     * @return int
     */
    @Override
    public long getItemId(int position) {
        return 0;
    }

    /**
     * リストの中に表示するViewを設定する
     * @param position int
     * @param convertView View
     * @param parent ViewGroup
     * @return View
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        SampleRowBinding binding;
        if(convertView == null){
            // sample_row.xmlからViewを作成
            binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.sample_row, parent, false);
            convertView = binding.getRoot();
            // BindingClassをConvertViewのTagに追加
            convertView.setTag(binding);
        } else{
            // ConvertViewのtagから取り出す
            binding = (SampleRowBinding) convertView.getTag();
        }

        // Model情報を取得する
        SampleModel model = getItem(position);

        // sample.xmlのtextと紐付ける
        binding.setModel(model);
        // 通信して画像を読み込み処理
        Picasso.with(parent.getContext()).load(model.getImageUrl()).into(binding.image);

        return convertView;
    }
}

このままだと色々コメント書いてますが、
意味わからないと思いますww

Adapterの実装に必要な物はクラスに対して、
Adapterを継承してあげる「extends BaseAdapter」からスタート!

BaseAdapterはAbstractClassになっているので、
実装すべきメソッドをOverrideして利用する。
BaseAdapterの場合は4つのメソッドを必須で実装する必要があります。

public int getCount(){}
public Object getItem(int position){}
public long getItemId(int position){}
public View getView(int position, View convertView, ViewGroup parent){}

その中でも必須で実装が必要な重要メソッドが下記2つ!

public int getCount(){}
public View getView(int position, View convertView, ViewGroup parent){}

重要な2つを説明します。

public int getCount(){}

リスト表示の数を返すメソッドです。
10だったら、10個のリスト
100だったら、100このリストが出来上がります。
これ実装しなかったらリスト表示できないのでご注意!!

public View getView(int position, View convertView, ViewGroup parent)

リストの1行に表示するためのViewと、データを紐付ける役割をgetViewは担っています。
リスト項目一つ一つに対して、呼ばれるので重い処理を書くのはNG!
可能な限り必要最小限のロジックにとどめましょう!

int position

リストのポジションです。1番目のリストはpositionが0となります。
2番目は1、3番目は2と続いていき、getCountメソッドの数まで呼び出しが行われます。

View convertView

あとで詳細を説明しますが、リスト1行に表示するViewが引数で渡されてきます。
getView()のreturn Viewとの兼ね合いで初期はNullになります。

ViewGroup parent

リスト表示を設定する親になります。
今回はListViewかな

というところで、getViewとgetCount最小限この2つを実装しないと、
アプリ開いた時に強制終了したり、表示されないという事が起こりますw

続いて、ListViewとの紐付け処理
またまたプログラムどん!w

// ListViewに表示するようのデータ格納配列を生成
mModels = new ArrayList<>();
// Adapterクラスにデータを受け渡す
mAdapter = new SampleAdapter(mModels);
// ListViewとAdapterを紐付ける
mBinding.listview.setAdapter(mAdapter);

Adapterを生成して、ListViewのメソッドsetAdapterにぽーいってやるだけ!
紐付けは簡単!

そうリスト表示で一番大変なのはAdapterだと思っている!w

そしてサンプルアプリではボタンを押したらリストが増えていくようにしています!
更新処理に関してどのようにしているのかという部分は下記!どん!!

// 増やすボタン押下時
mBinding.button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // リスト表示用配列にデータを追加
        mModels.addAll(createModels(mJsonArray));
        // データを追加した後、Adapterを更新
        // 更新するとリストの数が増える
        mAdapter.notifyDataSetChanged();
    }
});

Adapterに渡した配列に新しくObjectを追加しています。
その後「notifyDataSetChanged」メソッドを活用してリストを更新する事ができます!
増やしたり、減らしたりしたときの後には必ずセットでnotifyDataSetChangedを呼びましょう!
それで更新できますw

ではでは次は注意点

ListViewの注意点

リストViewの注意ポイントが一つ!
Viewを使いまわしている!という点です。

図にしたほうがわかりやすいのでそうします。
f:id:kamiya-kizuku:20171226194515p:plain

伝わりますか???w
画面の表示領域から、スライドして下のリストを表示した場合、
図の解説でいくと、リストの1番目が5番目として使いまわされる。
という点です。

なにが注意点だ???
となるかもしれないですが、
想像してみてください。

たとえば、テキストを設定する。
図の中の5番目はテキスト設定しないので、
設定処理を省いたとしましょう。
その場合、テキストが使い回しで設定していないので、
1番目に表示していたテキストがでてきちゃう。
という状態になります。
なので、使い回しを最初から意識して開発しないと予期せぬバグを生む可能性がありますのでご注意を!

DataBindingとViewHolderの組み合わせ

先程注意点で使いまわされている。
という話をしました。
そもそもなぜ使いまわされるのか?
という点で、getViewのreturnで返却しているViewが、getViewの引数にまたやってくる感じになります。
大量のリスト表示を行う際に、上から下まで全部描画するとアプリが超重くなるとう所に配慮しています。

さらに高速化するために、ViewHolderパターンという物があります。
公式サイトにも記載有り
developer.android.com

そのViewHolderパターンとDataBindingが相性が良いので、
ついでに軽く説明します。

そもそもViewHolderとは、
ActivityとかViewからテキストを取得する際に利用しているメソッド、
findViewByID
というのがありますね。
その処理の中で、IDに紐づくViewを取得するためにぐるぐるぐる頑張っています。
それをListViewの1行1行かなりのスピードで更新が必要な箇所でやると、リスト表示がカクカクしちゃいますw
ViewHolderというクラスを使って、初めの1回のみViewを一時格納してコスト削減する!という物です。

DataBindingを使うと、そのViewHolderを作成するためのClassを作成する必要が無い。
というメリットがあります!
凄い!嬉しい!って感じでサンプルでも使ってます!

とりあえず、
ソースをどん!!

SampleRowBinding binding;
if(convertView == null){
    // sample_row.xmlからViewを作成
    binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.sample_row, parent, false);
    convertView = binding.getRoot();
    // BindingClassをConvertViewのTagに追加
    convertView.setTag(binding);
} else{
    // ConvertViewのtagから取り出す
    binding = (SampleRowBinding) convertView.getTag();
}

DataBindingのクラスを生成して、
ConvertViewのタグにぶち込む。
使い増しのViewが来た場合、ConvertViewのタグからDataBindingのクラスを抜き取る
これだけですむ。
というところが魅力的。

DataBindingの詳細に関しては、今回は書きませんが
今まではViewHolderというクラスを作成していた部分がごっそり無くなるので可読性が高まった感がありますw

まぁざっと説明していきましたが、
今回はこんなところで。

その他
いい感じで表示するために、Picassoっていうライブラリを活用してしれっと、画像を通信で読み込んでいたり、
assetsディレクトリからJSONファイルを読み込んだりしてますが省きましたw

Github

github.com
あげてますー

素材に関して

いつもお世話になっているPAKUTASOさんありがとうございます!!
www.pakutaso.com