ListView是一个常用到的控件,常常配合各种adapter一起用于显示列表内容。
关于列表显示:
(1)Listview:用来展示列表的view。
(2)adpater:适配器,是连接列表与待显示数据的桥梁,为listview提供数据源。
其中一个比较常用的方式是继承自BaseAdapter,灵活的定制自己的adapter。
/**
* Created by Administrator on 2016/10/6.
*/
public class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int i) {
return null;
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
return null;
}
}
如上,对于一个listview,如何显示呢?
(1)需要知道有多少行待显示 getCount();
(2)每一行要显示的view getView();
因而,需要实现上面的四种抽象方法。
一种常见的listview性能优化的方法是covertView+ViewHolder。
public class MyAdapter extends BaseAdapter{
String TAG = "MyAdapter";
class ViewHolder{
ImageView pic;
TextView nameTV;
}
private List dataList = null;
private Context context;
public MyAdapter(Context context, List dataList) {
this.context = context;
this.dataList = dataList;
}
public MyAdapter(List dataList) {
this.dataList = dataList;
}
@Override
public int getCount() {
Log.d(TAG,"getCount()");
return dataList.size();
}
@Override
public Object getItem(int i) {
Log.d(TAG,"getItem()");
return dataList.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null){
holder = new ViewHolder();
view = LayoutInflater.from(context).inflate(R.layout.listview_item,null);
holder.pic = (ImageView) view.findViewById(R.id.list_item_img);
holder.nameTV = (TextView)view.findViewById(R.id.list_tv);
//关联View和ViewHolder
view.setTag(holder);
Log.d(TAG,"position = "+i+" getView():创建新的view"+" "+view.hashCode());
}else {
holder = (ViewHolder) view.getTag();
Log.d(TAG,"position = "+i+" getView():复用view"+" "+view.hashCode());
}
MyListItem myListItem = (MyListItem) dataList.get(i);
holder.pic.setImageDrawable(ContextCompat.getDrawable(context,myListItem.picId));
holder.nameTV.setText(myListItem.name);
return view;
}
}
如上,是实现adapter的一种优化方法,重点在于getView方法。一般,对于初学者常常有两个疑问:
(1)getView方法中参数View是什么?
(2)为何要用ViewHolder?
对于第一个问题:
首先,我们知道getView方法用来为每一个item行提供view对象,可以试想,如果我们有成千上万个item待显示,如果我们对每一个item都使用new来新建一个view对象,必定会占用很大的资源,很容易OOM崩溃。因而Android中提供了一个叫做反复循环器的构件(Recycler),用于对滑出显示界面的view实现复用,从而不用new出那么多的view对象。关于Recycler原理可参见http://blog.csdn.net/bill_ming/article/details/8817172
而getView方法的view参数就是Recycler中滑出显示界面外的view,最其内的子view重新赋值可以实现复用。可以这样理解:假设屏幕最多能显示10个item,则对于position = 0-9的item进行绘制调用getView时,getView方法的view参数为null,这时需要inflate来new一个view。而当listview上滑,显示position = 10、11、…时,getView的view参数不等于null,此时是复用的滑出屏幕外的view,关于这一点之后可以用代码验证。
对于第二个问题:
我们很容易想到,实现view复用,不用viewHolder也是可以实现的,大概如下:
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view == null){
view = LayoutInflater.from(context).inflate(R.layout.listview_item,null);
}
//为对应子view填充内容
((TextView)view.findViewById(R.id.list_tv)).setText("text");
return view;
}
这样做似乎也可以,但可以发现,不管view是否复用,为子view填充内容的时候都要进行findViewById操作,而这一操作是极为耗性能的操作。因而,就有了ViewHolder,使用ViewHolder模式来避免没有必要的调用findViewById(),关于ViewHolder的作用,官方给的说明是:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById()。
综上:
(1)covertView(就是getCount参数中的view)可以实现view复用。
(2)ViewHolder可以减少findViewById的次数,提高性能。
此外,关于ViewHolder是否定义为static还待讨论:
若为static,容易引起内存泄漏问题。优点则是ViewHolder的使用不依赖于外部类的创建。
代码验证:
如上,我的listview最多能显示11项,当position=0-10,都是创建新的view。滑动listview,显示position 为11、12…都是复用view,注意观察复用view后面打印的对应view的hasCode,即可知道复用的是滑出屏幕外的view。