解决方法
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,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,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,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,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; }
我们完成了
有些事情要考虑:
>如果您想要更改一行中的多个视图,该怎么改?作为一个挑战,做一个ListView,每一行都有两个TextView对象和一个ImageView
>调试您的ListView时,请检查一些事情,以便您可以真正了解发生的情况:
> ViewHolder的构造函数调用多少次.>在getView()结尾更新之前,holder.textView.getText()的值是多少?