ViewHolder - 良好的做​​法

Gor*_*ets 10 android android-viewholder

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

min*_*rus 29

您将拥有多个ViewHolder对象.

A ListView本质上不会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;
}
Run Code Online (Sandbox Code Playgroud)

这会有效,但请花点时间看看你是否能发现这里的低效率.想想上面哪些代码将被冗余调用.

问题是我们row.findViewById一遍又一遍地打电话,即使在我们第一次查看之后,它也永远不会改变.虽然如果你TextView的列表中只有一个简单的,它可能没那么糟糕,如果你有一个复杂的布局,或者你有多个想要设置数据的视图,你可能会失去一些时间来反复查找你的视图再次.

那么我们如何解决这个问题呢?好吧,在我们查找之后将TextView存储在某个地方是有意义的.因此,我们引入了一个名为a的类ViewHolder,它"持有"视图.所以在适配器内部引入一个内部类,如下所示:

private static class ViewHolder {
    TextView textView;
}
Run Code Online (Sandbox Code Playgroud)

这个类是私有的,因为它只是适配器的缓存机制,它是静态的,因此我们不需要引用适配器来使用它.

这将存储我们的视图,以便我们不必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();
}
Run Code Online (Sandbox Code Playgroud)

现在,我们只需要更新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;
}
Run Code Online (Sandbox Code Playgroud)

我们完成了!

有些事情要考虑:

  1. 如果您要在一行中有多个要更改的视图,这会如何变化?作为挑战,创建一个ListView,其中每行有两个TextView对象和一个ImageView
  2. 在调试ListView时,请检查一些内容,以便您可以真正了解正在发生的事情:
    1. 调用ViewHolder的构造函数的次数.
    2. holder.textView.getText()在你最后更新它之前的价值是什么getView()