読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

RecyclerViewで一度表示されてから行ごとのViewHolderが切り替わる場合のログを眺める

の続き。

レイアウトの切り替えを伴うRecyclerViewの切り替えがうまく行くとはにわかに信じがたかったので、 タップしたら簡単にレイアウトを切り替えるように前回のコードを少し書き換えてログを見てみました。

まとめ

当たり前のことを確かめました。

  • レイアウトが切り替わるようにViewTypeを切り替えていくと、最初のうちは、同じItemのカテゴリを切り替える(=Layoutを切り替える)たびにそのレイアウトに対応したViewHolderが生成される
  • ViewType一周分のViewHolderを生成したら、それ以上ViewTypeを切り替えても新しいViewHolderは生成されない
  • 上記は、同じpositionItemに対して複数ViewHolderが生成されているというわけでもなく、Adapterで扱っている範囲で余剰のViewHolderを生成している
  • なので、いくらか余剰のViewHolderが生成されたら、他のpositionItemを1つ2つ変化させたくらいではもう生成されない。
  • ViewHolderの種類のバランスをわざと崩すと新しいレイアウトに対応したViewHolderが生成される

おまけ

実装中にLayoutPositionAdapterPositionの違いが気になって、下記のStackOverFlowの質問を見て、へええと思ったのでこちらもメモ。

http://stackoverflow.com/questions/29684154/recyclerview-viewholder-getlayoutposition-vs-getadapterposition

  • LayoutPositionAdapterPositionViewHolderで使うレイアウトを生成している間はずれる可能性があるが、ほとんどの間同じ値を取る
  • notifyDataSetChangedで更新したいレイアウトにまつわる処理が重ければ、AdapterPositionRecyclerView#NO_POSITION(-1) になることもある
    • 1個ずつのアイテムの挿入、削除に関してはそこまで心配要らない
  • 何か不都合があって、AdapterPosition が取得できなかった場合は処理をしない、という使い方もある
  • LinearLayoutManager を使っていて、いままさにユーザーのタップしたItempositionを取得したいという場合は、LayoutPositionがよさそう

書き換えた部分

public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {

    private final LayoutInflater inflater;
    private final List<SampleItem> data;

    public SampleAdapter(Context context, List<SampleItem> data) {
        this.inflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case 1:
                Log.d("onCreateViewHolder",  "viewType: 1");
                return new Sample1ItemViewHolder(inflater.inflate(Sample1ItemViewHolder.LAYOUT_ID, parent, false));
            case 2:
                Log.d("onCreateViewHolder",  "viewType: 2");
                return new Sample2ItemViewHolder(inflater.inflate(Sample2ItemViewHolder.LAYOUT_ID, parent, false));
            case 3:
                Log.d("onCreateViewHolder",  "viewType: 3");
                return new Sample3ItemViewHolder(inflater.inflate(Sample3ItemViewHolder.LAYOUT_ID, parent, false));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        switch (getItemViewType(position)) {
            case 1:
                Log.d("onBindViewHolder",  "viewType: 1 position: " + position);
                ((Sample1ItemViewHolder)holder).onBindItemViewHolder(data.get(position));
                break;
            case 2:
                Log.d("onBindViewHolder",  "viewType: 2 position: " + position);
                ((Sample2ItemViewHolder)holder).onBindItemViewHolder(data.get(position));
                break;
            case 3:
                Log.d("onBindViewHolder",  "viewType: 3 position: " + position);
                ((Sample3ItemViewHolder)holder).onBindItemViewHolder(data.get(position));
                break;
        }

    }

    /* getItemViewType(), getItemCount() は変更ないし特に変わったことしてないので略 */

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);
        }
        public void changeCategory() {
            SampleItem item = data.get(this.getAdapterPosition());
            Log.d("changeCategory", "text: " + item.getText() + " category:" + item.getCategory());
            if(item.getCategory() == 3) {
                item.setCategory(1);
            } else {
                item.setCategory(item.getCategory() + 1);
            }
            notifyItemChanged(this.getAdapterPosition());
        }
    }

    public class Sample1ItemViewHolder extends ViewHolder {
        public static final int LAYOUT_ID = R.layout.sample_1_item_view;
        public final TextView textView;

        public Sample1ItemViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.textView1);
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    changeCategory();
                }
            });
        }

        public void onBindItemViewHolder(SampleItem data) {
            this.textView.setText(data.getText());
        }
    }

    /* Sample2ItemViewHolder, Sample2ItemViewHolderも同様なので略 */
}

ログ

// パッケージ名を切り取ったLogcatのログ。
// 表示されているItem分のRecyclerViewを一通り生成し終えた後から

// 最初のうちは、同じItemのカテゴリを切り替える(=Layoutを切り替える)たびに
// そのレイアウトに対応したViewHolderが生成される
11-03 23:00:35.691  D/changeCategory﹕ text: Data0 category:1
11-03 23:00:35.729  D/onCreateViewHolder﹕ viewType: 2
11-03 23:00:35.762  D/onBindViewHolder﹕ viewType: 2 position: 17
11-03 23:00:35.765  D/onCreateViewHolder﹕ viewType: 2
11-03 23:00:35.770  D/onBindViewHolder﹕ viewType: 2 position: 0
11-03 23:00:37.130  D/changeCategory﹕ text: Data0 category:2
11-03 23:00:37.162  D/onCreateViewHolder﹕ viewType: 3

// ViewType一周分のViewHolderを生成したら、
// それ以上ViewTypeを切り替えても新しいViewHolderは生成されない
11-03 23:00:37.177  D/onBindViewHolder﹕ viewType: 3 position: 0
11-03 23:00:38.116  D/changeCategory﹕ text: Data0 category:3
11-03 23:00:38.139  D/onBindViewHolder﹕ viewType: 1 position: 0
11-03 23:00:39.690  D/changeCategory﹕ text: Data0 category:1
11-03 23:00:39.714  D/onBindViewHolder﹕ viewType: 2 position: 0
11-03 23:00:40.321  D/changeCategory﹕ text: Data0 category:2
11-03 23:00:40.339  D/onBindViewHolder﹕ viewType: 3 position: 0
11-03 23:00:40.891  D/changeCategory﹕ text: Data0 category:3
11-03 23:00:40.910  D/onBindViewHolder﹕ viewType: 1 position: 0
11-03 23:01:00.454  D/changeCategory﹕ text: Data0 category:1
11-03 23:01:00.482  D/onBindViewHolder﹕ viewType: 2 position: 0
11-03 23:01:04.044  D/changeCategory﹕ text: Data0 category:2
11-03 23:01:04.059  D/onBindViewHolder﹕ viewType: 3 position: 0
11-03 23:01:04.429  D/changeCategory﹕ text: Data0 category:3
11-03 23:01:04.432  D/onBindViewHolder﹕ viewType: 1 position: 0
11-03 23:01:06.740  D/changeCategory﹕ text: Data1 category:1

// 上記は、同じpositionのItemに対して複数のViewHolderが生成されているというわけでもなく、
// Adapterで扱っている範囲で余剰のViewHolderを生成している。
// なので、いくらか余剰のViewHolderが生成されたら、
// 他のpositionのItemを1つ2つ変化させたくらいではもう生成されない。
11-03 23:01:06.762  D/onBindViewHolder﹕ viewType: 2 position: 1
11-03 23:01:10.153  D/changeCategory﹕ text: Data1 category:2
11-03 23:01:10.181  D/onBindViewHolder﹕ viewType: 3 position: 1
11-03 23:01:11.010  D/changeCategory﹕ text: Data1 category:3
11-03 23:01:11.030  D/onBindViewHolder﹕ viewType: 1 position: 1
11-03 23:01:13.143  D/changeCategory﹕ text: Data1 category:1
11-03 23:01:13.160  D/onBindViewHolder﹕ viewType: 2 position: 1
11-03 23:01:13.683  D/changeCategory﹕ text: Data1 category:2
11-03 23:01:13.712  D/onBindViewHolder﹕ viewType: 3 position: 1
11-03 23:01:13.920  D/changeCategory﹕ text: Data1 category:3
11-03 23:01:13.932  D/onBindViewHolder﹕ viewType: 1 position: 1
11-03 23:01:18.567  D/changeCategory﹕ text: Data4 category:1
11-03 23:01:18.591  D/onBindViewHolder﹕ viewType: 2 position: 4
11-03 23:01:21.577  D/changeCategory﹕ text: Data4 category:2
11-03 23:01:21.589  D/onBindViewHolder﹕ viewType: 3 position: 4
11-03 23:01:22.351  D/changeCategory﹕ text: Data4 category:3
11-03 23:01:22.380  D/onBindViewHolder﹕ viewType: 1 position: 4
11-03 23:01:25.889  D/changeCategory﹕ text: Data5 category:1
11-03 23:01:25.910  D/onBindViewHolder﹕ viewType: 2 position: 5
11-03 23:01:26.623  D/changeCategory﹕ text: Data5 category:2
11-03 23:01:26.642  D/onBindViewHolder﹕ viewType: 3 position: 5
11-03 23:01:27.601  D/changeCategory﹕ text: Data6 category:1
11-03 23:01:27.631  D/onBindViewHolder﹕ viewType: 2 position: 6

// ViewHolderの種類のバランスをわざと崩すと新しいレイアウトに
// 対応したViewHolderが生成される
11-03 23:01:29.853  D/changeCategory﹕ text: Data9 category:1
11-03 23:01:29.881  D/onCreateViewHolder﹕ viewType: 2
11-03 23:01:29.899  D/onBindViewHolder﹕ viewType: 2 position: 9
11-03 23:01:33.065  D/changeCategory﹕ text: Data10 category:1
11-03 23:01:33.091  D/onCreateViewHolder﹕ viewType: 2
11-03 23:01:33.091  D/onBindViewHolder﹕ viewType: 2 position: 10
11-03 23:01:33.721  D/changeCategory﹕ text: Data11 category:1
11-03 23:01:33.742  D/onCreateViewHolder﹕ viewType: 2
11-03 23:01:33.742  D/onBindViewHolder﹕ viewType: 2 position: 11