在APP界,不管是微信还是微博,都会使用到ListView这一基本的控件,所以学好ListView有多么重要。所谓“工欲善其事必先利其器”,要想开发更加优质的APP,那么就需要花点精力研究下ListView啦。本节课,我想探讨下ListView的自定义原理,以不变应万变。
-----------------------------分割线--------------------------------
在讲自定义的ListView之前,先简单回顾下ListView的实现原理,即ListView(列表控件)——Adapter(适配器)——Data(数据)。关键的地方就是Adapter,系统自带的Adpater有ArrayAdapter和SimpleAdapter,前者只可以展示简单的文字,后者能展示图片和文字。但是,系统自带的Adapter无法满足个性化需求,那就需要进行自定义。其实,自定义的ListView,最主要的还是对Adapter进行自定义。新建adapter,继承BaseAdapter,代码如下:
<span style="font-size:14px;">import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class NewAdapter extends BaseAdapter {
@Override
public int getCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
return null;
}
}
</span>
继承BaseAdapter之后,需要重新定义四个方法,如上所示。其中最重要的是getView(int position,View convertView, ViewGroup parent),一开始我是完全看不懂这个方法里面的参数,后来找到一些源码的注释,才算有些理解。我一开始查看了BaseAdapter的源码,发现没有这个方法,然后发现BaseAdapter继承了ListAdapter,不过也没有在ListAdapter中发现这个方法,直到追查到Adapter,才发现以下的一些注释。
翻译:获取展示数据集中特定位置的数据的视图。你可以手动地创建一个视图,或者从一个XML布局文件中填充出来。当视图被填充后,父视图(GridView, ListView...)会应用默认的参数,除非你使用inflate(int, ViewGroup, boolean)来绑定特定的根视图和避免依附根。
参数 position 在我们想要视图的适配器数据集中的项目位置。
参数 convertView 指的是可以重复使用的旧的视图。注意:在使用前,你应该检查这个视图不为空以及类型合适。如果无法将视图转换成能显示正确数据的视图,那么这个方法可以创建一个新的视图。复杂的列表能指定他们视图类型的数字,所以视图能一直保持正确的类型。
参数 parent 视图最终会依附的父类
返回 绑定特定位置数据的视图
翻译得不是很完美,请见谅。在此,我再重复强调一点,当屏幕进行滑动的时候,ListView中会有一部分View消失掉,但是convertView会重复使用这个实例化的view,那么就增加了效率。所以convertView可以理解为重复使用的view,那么在初始化的时候,就需要对convertView进行判断,如果为空,那么就要赋值,如果不为空,那么就直接调用。除此之外,还要引入一个新的概念ViewHolder,一个自定义的控件集合类。代码如下:
<span style="font-size:14px;">@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder = null;
if(convertView == null){
//mInflater的类型是LayoutInflater,item_listview是item的xml文件
convertView = mInflater.inflate(R.layout.item_listview, parent,false);
holder = new ViewHolder();//实例化一个控件类
holder.mName = (TextView) convertView.findViewById(R.id.textName);//根据id创建一个view,然后赋值给viewHolder类
holder.mPhone = (TextView) convertView.findViewById(R.id.textPhone);
holder.mAge = (TextView) convertView.findViewById(R.id.textAge);
convertView.setTag(holder);//最后将实例化的holder赋值给convertView
}else{
holder = (ViewHolder) convertView.getTag();//如果convertView不为空,那么就获取holder
}
Bean bean = mDatas.get(position);//bean是一个数据类,包括name,phone和age三个属性
holder.mName.setText(bean.getName());
holder.mPhone.setText(bean.getPhone());
holder.mAge.setText(bean.getAge()+"");
return convertView;//返回convertView
}
private class ViewHolder{
TextView mName;
TextView mPhone;
TextView mAge;
}</span>
getView()的基本逻辑是,首先判断convertView是否为空,如果为空,那么就实例化视图,然后赋值给ViewHolder类,最后利用setTag()赋值给convertView;如果不为空,那么就是之前已经赋值过了,可以直接用getTag()获取holder。当获取holder之后,就可以开始赋值了。最后返回convertView就可以了。而ViewHolder就是一个控件类。有了这个思路,就可以编写自定义的Adapter了。
关于R.layout.item_listview.xml和ListView与适配器的绑定,我就不写了。祝大家好运~