ViewHolder - 良好实践 [英] ViewHolder - good practice

查看:21
本文介绍了ViewHolder - 良好实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一个新手问题.为什么要在getView()中初始化ViewHolder?为什么不能在构造函数中初始化?

A little newbie question. Why should we initialize the ViewHolder in getView()? Why can't we initialize it in the constructor?

推荐答案

您将有多个 ViewHolder 对象存在.

You will have multiple ViewHolder objects in existence.

ListView 本质上不会为其每一行创建新的 View 实例.这样一来,如果您有一个包含一百万个事物的 ListView,您就不需要存储一百万个事物的布局信息.那么你需要存储什么?只是屏幕上的东西.然后,您可以一遍又一遍地重复使用这些视图.这样,一百万个对象的 ListView 可能只有 10 个子视图.

A ListView by its nature doesn't create new View instances for each of its rows. This is so that if you have a ListView of a million things, you don't need to store layout information for a million things. So what do you need to store? Just the things that are on the screen. You can then reuse those views over and over again. This way, your ListView of a million objects can just have maybe 10 child views.

在您的自定义数组适配器中,您将有一个名为 getView() 的函数,它看起来像这样:

In your custom array adapter, you will have a function called getView() that looks something like this:

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;
}

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

This will work, but take a moment and see if you can spot the inefficiency here. Think about which of the above code will be called redundantly.

问题是我们一遍又一遍地调用row.findViewById,即使在我们第一次查找之后,它也永远不会改变.如果您的列表中只有一个简单的 TextView,那可能还不错,如果您有一个复杂的布局,或者您有多个要为其设置数据的视图,则可能会丢失一些一次又一次地找到您的观点.

The problem is that we are calling row.findViewById over and over again, even though after the first time we look it up, it will never change. While if you only have a simple TextView in your list, it's probably not that bad, if you have a complex layout, or you have multiple views that you want to set data for, you could lose a bit of time finding your view over and over again.

那么我们如何解决这个问题呢?好吧,在我们查找之后将该 TextView 存储在某处是有意义的.所以我们引入了一个叫做 ViewHolder 的类,它持有"视图.所以在适配器内部,像这样引入一个内部类:

So how do we fix this? Well, it would make sense to store that TextView somewhere after we look it up. So we introduce a class called a ViewHolder, which "holds" the views. So inside of the adaptor, introduce an inner class like so:

private static class ViewHolder {
    TextView textView;
}

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

This class is private, since it's just a caching mechanism for the adapter, and it is static so that we don't need a reference to the adapter to use it.

这将存储我们的视图,以便我们不必多次调用 row.findViewById.我们应该在哪里设置它?当我们第一次膨胀视图时.我们将它存储在哪里?视图有一个自定义的标签"字段,可用于存储有关视图的元信息——正是我们想要的!然后,如果我们已经看过这个视图,我们只需要查找标签而不是查找行中的每个视图..

This will store our view so that we don't have to call row.findViewById multiple times. Where should we set it? When we inflate the view for the first time. Where do we store it? Views have a custom "tag" field, which can be used to store meta-information about the view - exactly what we want! Then, if we've already seen this view, we just have to look up the tag instead of looking up each of the views within the row..

所以 getView() 里面的 if 语句变成:

So the if statement inside of getView() becomes:

//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 的文本值,因为它已经是对回收视图的引用!所以我们最终的适配器代码变成了:

Now, we just have to update the holder.textView's text value, since it's already a reference to the recycled view! So our final adapter's code becomes:

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. How does this change if you have multiple views in a row that you want to change? As a challenge, make a ListView where each row has two TextView objects and an ImageView
  2. When debugging your ListView, check a few things so that you can really see what's going on:
  1. ViewHolder 的构造函数被调用了多少次.
  2. holder.textView.getText() 的值是什么,然后在 getView() 末尾更新它
  1. How many times ViewHolder's constructor is called.
  2. What the value of holder.textView.getText() is before you update it at the end of getView()

这篇关于ViewHolder - 良好实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆