ViewHolder – 良好的做​​法

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

Solutions Collecting From Web of "ViewHolder – 良好的做​​法"

您将拥有多个ViewHolder对象。

ListView根据其本性不会为其每个行创build新的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遍地调用row.findViewById ,即使在第一次查找之后,它永远不会改变。 而如果你的列表中只有一个简单的TextView ,那么可能并不是那么糟糕,如果你有一个复杂的布局,或者你有多个你想要设置数据的视图,你可能会失去一些时间来查找你的视图,再次。

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

 private static class ViewHolder { TextView textView; } 

这个类是私有的,因为它只是适配器的caching机制,并且是静态的,所以我们不需要引用适配器来使用它。

这将存储我们的视图,以便我们不必多次调用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. 在debuggingListView时,请检查一些事情,以便能够真正看到发生了什么事情:
    1. ViewHolder的构造函数调用了多less次。
    2. getView()的末尾更新holder.textView.getText()之前,它的值是holder.textView.getText()

当我们滚动列表每次行填充和新行视图为每一行创build我们需要初始化视图持有人。 就像我有两个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; }