10

少し初心者の質問です。ViewHolderinを初期化する必要があるのはなぜgetView()ですか? コンストラクターで初期化できないのはなぜですか?

4

2 に答える 2

29

複数のViewHolderオブジェクトが存在します。

Aはその性質上、各行にListView新しいインスタンスを作成しません。Viewこれは、百万ものものがある場合ListViewに、百万ものもののレイアウト情報を保存する必要がないようにするためです。では、何を保存する必要がありますか?画面に表示されているものだけです。その後、それらのビューを何度も再利用できます。このようにListView、100万個のオブジェクトのうち、おそらく10個の子ビューを持つことができます。

カスタム配列アダプターには、getView()次のような関数があります。

public View getView(int position, View convertView, ViewGroup parent) {
    //Here, position is the index in the list, the convertView is the view to be
    //recycled (or created), and parent is the ListView itself.

    //Grab the convertView as our row of the ListView
    View row = convertView;

    //If the row is null, it means that we aren't recycling anything - so we have
    //to inflate the layout ourselves.
    if(row == null) {
          LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          row = inflater.inflate(R.layout.list_item, parent, false);
    }

    //Now either row is the recycled view, or one that we've inflated. All that's left
    //to do is set the data of the row. In this case, assume that the row is just a
    //simple TextView
    TextView textView = (TextView) row.findViewById(R.id.listItemTextView);

    //Grab the item to be rendered. In this case, I'm just using a string, but
    //you will use your underlying object type.
    final String item = getItem(position);

    textView.setText(item);

    //and return the row
    return row;
}

これは機能しますが、少し時間を取って、ここで非効率性を見つけることができるかどうかを確認してください。上記のコードのどれが冗長に呼び出されるかを考えてください。

問題はrow.findViewById、最初に調べた後は決して変わらないのに、何度も何度も電話をかけていることです。リストに単純なものしかない場合でもTextView、それほど悪くはないでしょう。複雑なレイアウトがある場合、またはデータを設定する複数のビューがある場合は、ビューを何度も見つけるのに少し時間がかかる可能性があります。また。

では、これをどのように修正しますか?さて、私たちがそれを調べた後、そのTextViewをどこかに保存することは理にかなっています。そこでViewHolder、ビューを「保持」する、と呼ばれるクラスを紹介します。したがって、アダプタの内部で、次のような内部クラスを導入します。

private static class ViewHolder {
    TextView textView;
}

このクラスはプライベートです。これは、アダプターの単なるキャッシュメカニズムであり、静的であるため、使用するためにアダプターへの参照は必要ありません。

これにより、ビューが保存されるため、何度も呼び出す必要がありませんrow.findViewById。どこに設定すればいいですか?初めてビューを膨らませたとき。どこに保管しますか?ビューにはカスタムの「タグ」フィールドがあり、ビューに関するメタ情報を保存するために使用できます。まさに私たちが望むものです。次に、このビューをすでに表示している場合は、行内の各ビューを検索するのではなく、タグを検索する必要があります。

したがって、内部のifステートメントは次のようにgetView()なります。

//If the row is null, it means that we aren't recycling anything - so we have
//to inflate the layout ourselves.
ViewHolder holder = null;
if(row == null) {
    LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    row = inflater.inflate(R.layout.list_item, parent, false);
    //Now create the ViewHolder
    holder = new ViewHolder();
    //and set its textView field to the proper value
    holder.textView =  (TextView) row.findViewById(R.id.listItemTextView);
    //and store it as the 'tag' of our view
    row.setTag(holder);
} else {
    //We've already seen this one before!
    holder = (ViewHolder) row.getTag();
}

ここで、holder.textViewのテキスト値を更新する必要があります。これは、既にリサイクルされたビューへの参照であるためです。したがって、最終的なアダプタのコードは次のようになります。

public View getView(int position, View convertView, ViewGroup parent) {
    //Here, position is the index in the list, the convertView is the view to be
    //recycled (or created), and parent is the ListView itself.

    //Grab the convertView as our row of the ListView
    View row = convertView;

    //If the row is null, it means that we aren't recycling anything - so we have
    //to inflate the layout ourselves.
    ViewHolder holder = null;
    if(row == null) {
        LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        row = inflater.inflate(R.layout.list_item, parent, false);
        //Now create the ViewHolder
        holder = new ViewHolder();
        //and set its textView field to the proper value
        holder.textView =  (TextView) row.findViewById(R.id.listItemTextView);
        //and store it as the 'tag' of our view
        row.setTag(holder);
    } else {
        //We've already seen this one before!
        holder = (ViewHolder) row.getTag();
    }

    //Grab the item to be rendered. In this case, I'm just using a string, but
    //you will use your underlying object type.
    final String item = getItem(position);

    //And update the ViewHolder for this View's text to the correct text.
    holder.textView.setText(item);

    //and return the row
    return row;
}

これで完了です。

考えるべきいくつかのこと:

  1. 変更したいビューが連続して複数ある場合、これはどのように変更されますか?課題として、各行に2つのTextViewオブジェクトと1つのImageView
  2. ListViewをデバッグするときは、何が起こっているのかを実際に確認できるように、いくつかのことを確認してください。
    1. ViewHolderのコンストラクターが呼び出された回数。
    2. holder.textView.getText()最後に更新する前の値は何ですかgetView()
于 2012-10-31T18:40:56.933 に答える
3

行が移入され、すべての行に対して新しい行ビューが作成されるたびにリストをスクロールするため、ビューホルダーを初期化する必要があります。2 つの TextView が並んでいるのと同じように、

  static class ViewHolder {
        protected TextView title;
        protected TextView type;

    }


     public View getView(int position, View convertView, ViewGroup parent) {
            View view = null;
            if (convertView == null) {
                LayoutInflater inflator = context.getLayoutInflater();
                view = inflator.inflate(R.layout.feeds_rowview, null);
                final ViewHolder viewHolder = new ViewHolder();
                view.setTag(viewHolder);
                viewHolder.title = (TextView) view.findViewById(R.id.Title);
                viewHolder.type = (TextView) view.findViewById(R.id.Type);

            } else {
                view = convertView;
            }

            ViewHolder holder = (ViewHolder) view.getTag();
            holder.title.setText(list.get(position).getTitle());
            holder.type.setText(list.get(position).getType());

            return view;
     }
于 2012-10-31T18:15:00.627 に答える