listVeiw之adapter使用优化及item选中状态的处理
要给listView设置列表数据需要掌握adapter的使用,这也是最基本的用法,初次之外的好多面试中曾被问到这样的问题:你可以说说listView的优化吗?相信不少被问到,今天我们就来解决下这个问题;好了直接上代码:
继承BaseAdapter来使用ListView
以下是继承之后需要实现的方法,我们来具体看看每个方法的作用
-
getCount() 用来获取数据源中的数据对象个数
@Override
public int getCount() {
return mData.size();
}
-
getItem(int position) 用来获取指定位置处的数据源对象
@Override
public Object getItem(int position) {
return mData.get(position);
}
-
getItemId(int position) 获取数据源对象的Id,如果有的话如果数据源对象自己没有定义Id,则可以简单地返回其在数据源中的位置
@Override
public long getItemId(int position) {
return position;
}
-
getView(int position, View convertView, ViewGroup parent) 每当Android ListView需要显示一行时,它会调用此方法BaseAdapter是个抽象类,继承它能很方便的来使用ListView。要覆写四个方法。前三个都很简单。重点是getView方法,这直接决定你的屏幕加载是否顺滑。如果convertView为空,加载布局创建View若不空,则直接通过position,获取list中的item,直接替换view中的内容。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "显示:" + position + "行,调用getView()" + "参数convertView==null?" + (convertView == null));
View rootView = null;
//如果没有可以重用的控件
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
rootView = inflater.inflate(R.layout.my_list_item, parent, false); //加载布局,创建View
rootView.setTag(position);
Log.d(TAG, "实例化rootView,保存到Tag中的值为:" + position);
counter++;
Log.d(TAG, "控件实例化个数:" + counter);
} else {
//控件己经被创建过,直接重用
rootView = convertView;
}
//依据位置提取相应的数据源对象
MyDataClass item = mData.get(position);
//获取用于显示内容的控件的引用
TextView titleTextView = (TextView) rootView.findViewById(R.id.tvTitle);
TextView detailTextView = (TextView) rootView.findViewById(R.id.tvDetail);
//设置显示内容
titleTextView.setText(item.getTitle());
detailTextView.setText(item.getDetail() + " 本行Tag中保存的值为:" + rootView.getTag());
return rootView;
}
如果你要加载多种布局那么首先你一定重写这两个方法
-
getItemViewType(int position) 获取某个position位置的布局格式
@Override
public int getItemViewType(int position) {
if(position==2){
return 2;
}
return 1;
//根据具体情况来判断
}
-
getViewTypeCount() 这里来获取布局的种类有多少种
@Override
public int getViewTypeCount() {
return 2;
}
本文看到这里,感觉没有重点,如果是这样,那么这篇文章就等于白写了,实际运用中,我们最关键的就是用到getView()的优化来使得listView的item能够重复利用资源,从而大大的加快了加载的速度,体验更好,当然实际运用中可能你会遇到以下的难点,以下都是实际项目中血淋林的教训,在此罗列出来,下篇有专门写的demo分析这些问题
1.当listView的item中有选中状态效果,比如用到最多的地方,像购物车的例子这样,有选中的判断,如果状态复用的话选中就会错乱,针对这样的问题该怎么解决;
2.再次讲讲不同布局在listview中是怎么使用的;
3.当每个item需要添加实时刷新的数据的时候,设置点击的时候会失去状态,这是为甚么,该怎么解决;
4.以上几个都有分开写的demo,最后还有一个综合的listView写的购物车例子,之后与大家分享。
接下来我们就开始在实战中学习战争
1.分析需求
效果图不只是一个单纯的列表(里面有点击选中的按钮,所以一般的复用模式得适当改变),如下图就是实现的效果:
上图,我们实现的效果就是实现listview中的两种不同布局的填充,其次就是当列表中有选中状态的时候如何避免滑动时的状态复用,针对这两个问题,我们来看代码:
@Override
public int getItemViewType(int position) {
if (getItemId(position) == 3 || getItemId(position) == 10) {
return 1;
}
return 0;
}
@Override
public int getViewTypeCount() {
return 2;
}
- 在Adapter中要实现两种甚至多种布局需要重写以上两个方法,前面已经详细的介绍过这两个方法的用途,在这里我只是做了简单的处理,针对不同的情况做法当然不一样。==getItemViewType(int position)== 当item的位置为3或者是10的时候,给出的布局格式是1,否则的话给的是0。==getViewTypeCount()== 此方法返回这个列表中需要几种类型的布局,此处返回两种布局类型,当然要在==getView()== 中做处理,继续来看以下的代码:
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
final ViewHolder viewHolder;
if (view == null) {
viewHolder = new ViewHolder();
if (getItemViewType(i) == 0) {
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.iten_view, null);
} else {
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_teo, null);
}
viewHolder.cbbox = (CheckBox) view.findViewById(R.id.cb_my);
viewHolder.tvMy = (TextView) view.findViewById(R.id.tv_coments);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
return view;
private class ViewHolder {
public CheckBox cbbox;
public TextView tvMy;
}
- 其中我们在getView方法中做了布局的复用,首先我们判断view是否为空,其次就是ViewHolder的使用,其实就是一个持有者,不需要每次加载的时候都初始化控件,大大的加快了速度,其中我们调用判断getItemViewType(i)==0时,我们初始化一种布局,如果这个位置上的view类型等于1的话就初始化另一种布局,当然给不同布局界面填充数据的时候,也要做不同的判断,针对不同布局的界面也是不一样的,代码就不写了,自己领悟下;不出问题,界面的多布局问题,我们就解决了,接着来看我们复用导致的选中状态错乱的问题
选中状态错乱的问题
- 首先导致选中错乱的原因
在getView方法中做了布局的复用,导致除了布局复用之外,选中的状态也复用了,这样,解决这个问题的方式有好几种吧,不过我只讲一种,因为我只会一种,好吧,我们继续,首先,我们要给每个item设置一个状态:
private List<Boolean> booleenList;
//声明一个boolean类型的集合
~~~
//初始化数据
private void initData() {
lvtext = (ListView) this.findViewById(R.id.lv_lv);
myAdapter = new MyAdapter();
stringList = new ArrayList<>();
booleenList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
stringList.add("im is" + i + "name");
booleenList.add(false);
//初始化数据的时候每个数据加个false的状态
}
}
~~~
myAdapter.setData(stringList);
myAdapter.setBoolData(booleenList);
//这个步骤很明显就是传值
接下来看我在adapter中是怎么处理的
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
final ViewHolder viewHolder;
if (view == null) {
viewHolder = new ViewHolder();
if (getItemViewType(i) == 0) {
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.iten_view, null);
} else {
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_teo, null);
}
viewHolder.cbbox = (CheckBox) view.findViewById(R.id.cb_my);
viewHolder.tvMy = (TextView) view.findViewById(R.id.tv_coments);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tvMy.setText(data.get(i));
//注意以下这行代码
viewHolder.cbbox.setChecked(boolData.get(i));
viewHolder.cbbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//这里直接调用方法
//MainActivity.selectChange(i, boolData);
//自定义的接口
slecct.seleckt(i, boolData);
}
});
return view;
}
- 以上代码三个地方有备注,全是有关于处理选中状态丢失的,先看第一个 ==viewHolder.cbbox.setChecked(boolData.get(i));== 这里我们根据传过来的boolean数据来做出判断选择处理,如果这个位置上的boolData是true的时候就选中,否则的话就不选,多加了一个判断,至于怎么让集合的某个选中的变成true,就要在接下来的点击事件中做处理,==MainActivity.selectChange(i, boolData);==这里我们调用了activity中的方法来处理的,继续看以下代码:
//可以直接使用
public static void selectChange(int i, List<Boolean> boolData) {
if (boolData.get(i) == true) {
boolData.set(i, false);
myAdapter.notifyDataSetChanged();
} else if (boolData.get(i) == false) {
boolData.set(i, true);
myAdapter.notifyDataSetChanged();
}
}
- 很简单的逻辑就是在点击之后,如果这个位置是true就变换成false,如果是false就变换成true,然后刷新列表,这里写了一个静态类,可以直接调用就可;
以上的代码,暂且到这里好了,这些代码比较简单,我自己有写具体的demo,但是没有上传到github,因为这代码量确实少,还希望自己看着贴出的代码了解了解吧!,下一篇的时候,我们继续讲讲listview的item的点击事件的一些问题吧,实际开发中遇到
有兴趣的可以继续学习下一篇:listView之item点击失效,长按消失了的问题