web-dev-qa-db-ja.com

findViewByIdとListViewアダプターのビューホルダーパターン

LayoutInflaterfindViewByIdメソッドで新しいアイテムを作成するには、常にgetViewAdapterを使用します。

しかし、多くの記事でfindViewByIdは非常に遅いため、View Holderパターンを使用することを強くお勧めします。

誰がfindViewByIdがそんなに遅いのか説明できますか?そして、なぜビューホルダーパターンが速いのですか?

ListViewに異なるアイテムを追加する必要がある場合はどうすればよいですか?タイプごとにクラスを作成する必要がありますか?

static class ViewHolderItem1 {
    TextView textViewItem;
}

static class ViewHolderItem2 {
    Button btnViewItem;
}
static class ViewHolderItem3 {
    Button btnViewItem;
    ImageView imgViewItem;
}
49
Suvitruf

FindViewByIdがなぜ遅いのか、誰でも説明できますか?そして、なぜView Holderパターンが速いのですか?

Holderを使用していない場合、getView()メソッドは、行が表示されなくなるたびにfindViewById()を呼び出します。そのため、リストに1000行があり、990行が表示されない場合、990回はfindViewById()と呼ばれます。

Holderデザインパターンは、Viewキャッシングに使用されます-Holder(任意)オブジェクトは、各行の子ウィジェットを保持し、行がViewの外にある場合、findViewById()は呼び出されませんが、Viewはリサイクルされ、WidgetはHolderから取得されます。

if (convertView == null) {
   convertView = inflater.inflate(layout, null, false);
   holder = new Holder(convertView);
   convertView.setTag(holder); // setting Holder as arbitrary object for row
}
else { // view recycling
   // row already contains Holder object
   holder = (Holder) convertView.getTag();
}

// set up row data from holder
titleText.setText(holder.getTitle().getText().toString());

Holderクラスは次のようになります。

public class Holder {

   private View row;
   private TextView title;

   public Holder(View row) {
      this.row = row;
   }

   public TextView getTitle() {
      if (title == null) {
         title = (TextView) row.findViewById(R.id.title);
      }
      return title;
   }
}

@meredricaが指摘したように、パフォーマンスを向上させたい場合は、パブリックフィールドを使用できます(ただし、カプセル化は破棄されます)。

更新:

ViewHolderパターンを使用する2番目の方法は次のとおりです。

ViewHolder holder;
// view is creating
if (convertView == null) {
   convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false);
   holder = new ViewHolder();   
   holder.title = (TextView) convertView.findViewById(R.id.title);
   holder.icon = (ImageView) convertView.findViewById(R.id.icon);
   convertView.setTag(holder);
}
// view is recycling
else {
   holder = (ViewHolder) convertView.getTag();
}

// set-up row
final MyItem item = mItems.get(position);
holder.title.setText(item.getTitle());
...

private static class ViewHolder {

   public TextView title;
   public ImageView icon;
}

アップデート#2:

誰もが知っているように、サポートライブラリとしてのGoogleとAppCompat v7は、アダプターベースのビューをレンダリングするために設計された RecyclerView と呼ばれる新しいViewGroupをリリースしました。 @ antonioleivapostで述べているように」グリッドビュー"

この要素を使用できるようにするには、基本的に最も重要なものが1つ必要です。また、前述のViewGroupにラップされている特別な種類のアダプターです-RecyclerView.AdapterここでViewHolderはここで話していることです:)単純に、この新しいViewGroup要素には独自のViewHolderパターンが実装されています。あなたがする必要があるのは、RecyclerView.ViewHolderから拡張する必要があるカスタムViewHolderクラスを作成することであり、現在のかどうかを確認する必要はありませんアダプタの行がnullかどうか。

アダプタはあなたのためにそれを行い、あなたはそれが膨張する必要がある場合にのみ行が膨張することを確実にすることができます(私が言うでしょう)。簡単な実装を次に示します。

public static class ViewHolder extends RecyclerView.ViewHolder {

   private TextView title;

   public ViewHolder(View root) {
      super(root);
      title = root.findViewById(R.id.title);
   }
}

ここで2つの重要なこと:

  • 行のルートビューを渡す必要があるコンストラクターsuper()を呼び出す必要があります
  • getPosition()メソッドを介してViewHolderから行の特定の位置を直接取得できます。これは、タップ後に何らかのアクションを実行する場合に便利です。1 行ウィジェット上。

また、AdapterでのViewHolderの使用。アダプタには、実装する必要がある3つのメソッドがあります。

  • onCreateViewHolder()-ViewHolderが作成される場所
  • onBindViewHolder()-行を更新する場所。行をリサイクルするコードの一部と言えます
  • getItemCount()-BaseAdapterの典型的なgetCount()メソッドと同じだと思います

ちょっとした例:

@Override 
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false);
   return new ViewHolder(root);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
   Item item = mItems.get(position);
   holder.title.setText(item.getTitle());
}

@Override public int getItemCount() {
   return mItems != null ? mItems.size() : 0;
}

1 RecyclerViewは、アイテムのクリックイベントをリッスンできるようにするための直接的なインターフェイスを提供していないことに言及しておくと良いでしょう。これは誰かにとって好奇心が強いかもしれませんが、 ここにニースの説明があります 実際に見えるほど好奇心がないのはなぜですか。

行(および行に必要なあらゆる種類のウィジェット)のクリックイベントを処理するために使用する独自のインターフェイスを作成することで、これを解決しました。

public interface RecyclerViewCallback<T> {

   public void onItemClick(T item, int position); 
}

コンストラクタを介してアダプタにバインドし、ViewHolderでそのコールバックを呼び出します。

root.setOnClickListener(new View.OnClickListener {
   @Override
   public void onClick(View v) {
      int position = getPosition();
      mCallback.onItemClick(mItems.get(position), position);
   }
});

これは基本的な例ですので、考えられる唯一の方法と考えないでください。可能性は無限です。

85
Simon Dorociak

ViewHolder patternは、ViewHolderの静的インスタンスを作成し、最初にロードされたときにそれをビューアイテムにアタッチし、その後の呼び出しでそのビュータグから取得されます。 getView()メソッドは非常に頻繁に呼び出されます。特に、リストビューの多くの要素がスクロールする場合、実際には、スクロール時にlistviewアイテムが表示されるたびに呼び出されます。

ViewHolderパターンは、findViewById()が何度も無駄に呼び出されるのを防ぎ、静的参照のビューを保持します。リソースを節約するのに適したパターンです(特にリストビューアイテムで多くのビューを参照する必要がある場合) 。

@RomainGuy

ViewHolderは、getView()でのメモリ割り当てを回避するために、一時的なデータ構造を保存するためにも使用できます。 ViewHolderには、Cursorからデータを取得する際の割り当てを回避するためのcharバッファーが含まれています。

5