2012-10-31 103 views
9

有点新手问题。我们为什么要初始化getView()中的ViewHolder?为什么我们不能在构造函数中初始化它?ViewHolder - 良好做法

+1

http://www.youtube.com/watch?v=wDBM6wVEO70年底更新之前。看看这个视频。应该回答你的问题。 – Raghunandan

回答

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

这是可行的,但需要一些时间,看看你是否可以在这里发现的低效率。考虑一下上面哪段代码会被重复调用。

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

那么我们如何解决这个问题呢?那么,在我们查看之后将TextView存储在某个地方是有意义的。所以我们引入一个名为ViewHolder的类,它可以“保留”视图。所以适配器的内部,引入一个内部类,像这样:

private static class ViewHolder { 
    TextView textView; 
} 

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

这将存储我们的视图,以便我们不必多次拨打row.findViewById。我们应该在哪里设置它?当我们第一次夸大观点时。我们在哪里存储它?视图有一个自定义的“标签”字段,可用于存储关于视图的元信息 - 正是我们想要的!然后,如果我们已经看到了这种观点,我们只需要查找的标签,而不是查找每个行内观点..

所以getView()内部的if语句变成:

//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()
+0

thnx,真棒答案 – Gorets

+0

没问题 - 让我知道,如果一切都合理,ViewHolder的东西肯定让我困惑了一阵子,当时我第一次了解它。 – mindvirus

+0

哇。优秀的答案。作为你的挑战;你能用一些参考来改进它吗? =) – Qw4z1

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

不,每个视图都有不同的值。因为我们在行中有不同的数据。 –