ViewHolder - 好习惯 [英] ViewHolder - good practice

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

问题描述

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

解决方案

您将有存在多个 ViewHolder 的对象。

A 的ListView 根据其性质不会为它的每一个行新的查看实例。这是这样,如果你有100万的事情一个的ListView ,你并不需要存储布局信息100万的事情。那么你需要存储?只是,在屏幕上的东西。然后,您可以一遍又一遍地重复这些观点。你的的ListView 一百万个对象通过这种方式,可以只可能有10子视图。

在自定义阵列适配器,你将有一个调用的函数 getView()看起来是这样的:

 公开查看getView(INT位置,查看convertView,ViewGroup中父){
    //这里,位置是该指数在名单中,convertView认为是
    //循环使用(或创建)和父是Lis​​tView控件本身。

    //抓住convertView作为我们行的ListView
    查看排= convertView;

    //如果该行是空,这意味着我们不回收任何东西 - 所以我们有
    //膨胀布局自己。
    如果(行== NULL){
          LayoutInflater充气=(LayoutInflater)的getContext()getSystemService(Context.LAYOUT_INFLATER_SERVICE)。
          行= inflater.inflate(R.layout.list_item,父母,假);
    }

    //现在无论是行是循环来看,还是一个我们已经膨胀。剩下的
    //做的是设置行的数据。在这种情况下,假设行仅仅是一个
    //简单的TextView
    TextView中的TextView =(TextView中)row.findViewById(R.id.listItemTextView);

    //抓取该项目被渲染。在这种情况下,我只是用一个字符串,但
    //你会用你的潜在对象类型。
    最后弦乐项目=的getItem(位置);

    textView.setText(项目);

    //并返回行
    返回行;
}
 

这将工作,但花一点时间,看看你是否可以在这里发现的低效率。想想看,这上面的code将冗余调用。

现在的问题是,我们正在呼吁 row.findViewById 一遍又一遍,即使后的第一时间,我们看它,它永远不会改变。而如果你只有一个简单的的TextView 在列表中,它可能不是那么糟糕,如果你有一个复杂的布局,或者你有要设置数据的多个视图,你可能会失去一点时间一遍遍寻找你的看法。

那么,我们如何解决这个问题?那么,这将是有意义的存储TextView的,我们查出来后的地方。因此,我们引入一类叫做 ViewHolder ,其中持有的观点。因此,适配器的内部,引入内部类,像这样:

 私有静态类ViewHolder {
    的TextView TextView的;
}
 

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

这将保存我们的观点,使我们不必调用 row.findViewById 多次。我们应该在哪里设置呢?当我们充气视图首次。我们在哪里存放呢?次有一个自定义标签字段,它可用于存储关于视图的元信息 - 正是我们所希望!那么,如果我们已经看到了这个观点,我们只要看仰视每个视图的行内了标记,而不是..

所以,if语句里面 getView()变为:

  //如果该行是空,这意味着我们不回收任何东西 - 所以我们有
//膨胀布局自己。
ViewHolder支架=无效;
如果(行== NULL){
    LayoutInflater充气=(LayoutInflater)的getContext()getSystemService(Context.LAYOUT_INFLATER_SERVICE)。
    行= inflater.inflate(R.layout.list_item,父母,假);
    //现在创建ViewHolder
    持有人=新ViewHolder();
    //并设置其的TextView领域为正确的值
    holder.textView =(TextView中)row.findViewById(R.id.listItemTextView);
    //并保存它作为我们的观点的标签
    row.setTag(保持器);
} 其他 {
    //我们已经看到了这一个之前!
    支架=(ViewHolder)row.getTag();
}
 

现在,我们只需要更新holder.textView的文本价值,因为它已经是一个参考循环的看法!因此,我们的最终适配器的code就变成了:

 公开查看getView(INT位置,查看convertView,ViewGroup中父){
    //这里,位置是该指数在名单中,convertView认为是
    //循环使用(或创建)和父是Lis​​tView控件本身。

    //抓住convertView作为我们行的ListView
    查看排= convertView;

    //如果该行是空,这意味着我们不回收任何东西 - 所以我们有
    //膨胀布局自己。
    ViewHolder支架=无效;
    如果(行== NULL){
        LayoutInflater充气=(LayoutInflater)的getContext()getSystemService(Context.LAYOUT_INFLATER_SERVICE)。
        行= inflater.inflate(R.layout.list_item,父母,假);
        //现在创建ViewHolder
        持有人=新ViewHolder();
        //并设置其的TextView领域为正确的值
        holder.textView =(TextView中)row.findViewById(R.id.listItemTextView);
        //并保存它作为我们的观点的标签
        row.setTag(保持器);
    } 其他 {
        //我们已经看到了这一个之前!
        支架=(ViewHolder)row.getTag();
    }

    //抓取该项目被渲染。在这种情况下,我只是用一个字符串,但
    //你会用你的潜在对象类型。
    最后弦乐项目=的getItem(位置);

    //并更新ViewHolder这种观点的文本正确的文本。
    holder.textView.setText(项目);

    //并返回行
    返回行;
}
 

和我们就大功告成了!

有些事情要考虑:

  1. 如何,如果你有,你想改变一排多个视图这种变化?作为一个挑战,使一个ListView每行有两个的TextView 对象和的ImageView
  2. 在当前调试的ListView,查了几件事情,让你可以真正看到发生了什么:
    1. 多少次ViewHolder的构造函数被调用。
    2. 什么 holder.textView.getText()是在年底前更新它getView()的值

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

解决方案

You will have multiple ViewHolder objects in existence.

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.

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.

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.

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.

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..

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

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

And we're done!

Some things to think about:

  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. 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天全站免登陆