10

一个小新手问题。为什么我们要初始化ViewHolderin getView()?为什么我们不能在构造函数中初始化它?

4

2 回答 2

29

您将有多个ViewHolder对象存在。

AListView本质上不会View为其每一行创建新实例。这样一来,如果您有ListView一百万个事物中的一个,则不需要存储一百万个事物的布局信息。那么你需要存储什么?只是屏幕上的东西。然后,您可以一遍又一遍地重用这些视图。这样,ListView一百万个对象中可能只有 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的,它可能还不错,但如果您有一个复杂的布局,或者您有多个要为其设置数据的视图,您可能会浪费一些时间一遍又一遍地找到您的视图再次。

那么我们如何解决这个问题呢?好吧,在我们查找它之后将它存储在某个地方是有意义的。所以我们引入了一个名为 a 的类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. 如果您想要更改一行中有多个视图,这将如何变化?作为一个挑战,制作一个 ListView ,其中每行有两个TextView对象和一个ImageView
  2. 在调试您的 ListView 时,请检查几件事,以便您可以真正了解发生了什么:
    1. ViewHolder 的构造函数被调用了多少次。
    2. holder.textView.getText()在你更新它之前的值是什么getView()
于 2012-10-31T18:40:56.933 回答
3

当我们每次填充行并为每一行创建新的行视图时滚动列表时,我们需要初始化视图持有者。就像我有两个 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 回答