ListView使用总结
虽然随着RecyclerView
的不断普及,相应的资源也越来越多,许多的项目都在使用RecyclerView
,但作为他的前辈ListView
,加深对ListView
的使用有助于我们更好的适应到RecyclerView
的使用中。
首先看一下我们实现的效果一些简单效果
这只是前面的一些简单效果,后面会有一些进阶的效果,希望能耐心的看下去。
ListView的优化
ListView
的优化主要包括两个方面,分别是对自身的优化以及其适配器(Adapter
)的优化。
ListView
自身的优化
主要包括一条。对于ListView
的layout_height
和layout_width
设置为match_parent
,如果设置为match_parent
,一般ListView
的宽高会测量三次以上。具体的源码没有深入研究。但为什么会要测量多次,如果对于自定义View
稍微有点基础的会知道,对于View
的测量大小有三个类型:
- UNSPECIFIED
:未指定的,父类不对子类施加任何限制。
- EXACTLY
:确定的,父类确定其子类控件的大小。
- AT_MOST
:最大值,需要子类去测量自身大小确定。
如果我们设置宽高为wrap_content
,即AT_MOST
,表示其宽高有控件本身去测量确定,而如果是match_parent
,则EXACTLY
,表示确定的大小。
Adapter
优化。
对于Adapter的优化,主要包括以下几个步骤:
复用
convertView
,减少子布局的生成。定义
ViewHolder
,减少findViewById()
的次数。
下面看一下代码
/**
* 最基础的adapter
* Created by Alex_MaHao on 2016/5/17.
*/
public class SimpleBaseAdapter extends BaseAdapter {
private List<String> datas;
public SimpleBaseAdapter(List<String> datas) {
this.datas = datas;
}
@Override
public int getCount() {
return datas==null?0:datas.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vHolder;
if(convertView==null){
//初始化item布局
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_listview_sample,parent,false);
//创建记事本类
vHolder = new ViewHolder();
//查找控件,保存控件的引用
vHolder.tv = ((TextView) convertView.findViewById(R.id.listview_sample_tv));
//将当前viewHolder与converView绑定
convertView.setTag(vHolder);
}else{
//如果不为空,获取
vHolder = (ViewHolder) convertView.getTag();
}
vHolder.tv.setText(datas.get(position));
return convertView;
}
/**
* 笔记本类,保存对象的引用
*/
class ViewHolder{
TextView tv;
}
}
根据代码分析思路:
- ListView
中的item
滑出屏幕时,滑出的item
会在getView()
方法中返回,及converView
参数。所以在这里判断,是否为null,如果不为null,表示我们可以对该布局重新设置数据并返回到列表中显示。
- ViewHolder
,保存item
中子控件的引用。因为我们复用了converView
,那么对于同一个converView
布局,其子控件的引用应该是不变的。所以我们可以获取到其上的所有子控件的引用并通过ViewHolder
进行保存。
- setTag(),getTag()
:该方法实现了ViewHolder
和converView
的绑定,就类似于通过setTag()
方法,将ViewHolder
打包成一个包裹,放在了converView
上,再通过getTag()
方法,将这个包裹取出。
在很多优化中,会将
ViewHolder
定义为static
。但在这里我并没加上。因为经过测试,加或不加,ViewHolder
的创建次数不变。网上查了很多资料,也没有找到一个让我信服的理由。唯一有点理的就是基于java的特性。静态内部类的对象不依赖于其所在的外部类对象。
Adapter的封装–BaseAppAdapter
上一节说了Adapter
的优化,但如果我们每次写都要写这么多的优化代码,这不符合程序员懒惰的天性。那我们只能把他封装,提取出一个公共的基类,在基类中,我们把布局加载,优化等都默认实现,只需让子类构造布局文件,以及绑定数据。
那么,从我们上一节的代码看,有以下模块都可以提取为基类:
- 数据集合datas:对于数据集合,我们通常都是一个
List
集合,在这里定义泛型来表述其所包含的内容。 - 数据优化:布局的复用以及
ViewHolder
与converView
的绑定。 ViewHolder
:定义一个ViewHolder
,通过map
保存控件与id;
子类所需实现的:
- 数据的初始化
- 确定
item
的布局文件 - 将数据与视图绑定。
那么直接看一下我们继承好的代码:
public abstract class BaseAppAdapter<T> extends BaseAdapter {
/**
* 泛型,保存数据
*/
protected List<T> datas;
/**
* 构造方法,子类必须实现其构造方法,并初始化数据
* @param datas
*/
public BaseAppAdapter(List<T> datas) {
this.datas = datas;
}
@Override
public int getCount() {
return datas==null?0:datas.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BaseViewHolder vHolder;
if(convertView==null){
convertView = LayoutInflater.from(parent.getContext()).inflate(getItemLayoutId(),parent,false);
vHolder = new BaseViewHolder(convertView);
convertView.setTag(vHolder);
}else{
vHolder = (BaseViewHolder) convertView.getTag();
}
/**
* 数据绑定的回调
*/
bindData(vHolder,datas.get(position));
return convertView;
}
/**
* 子类实现,获取item布局文件的id
* @return
*/
protected abstract int getItemLayoutId();
/**
* 子类实现,绑定数据
* @param vHolder 对应position的ViewHolder
* @param data 对应的数据绑定
*/
protected abstract void