关于ListView,GridView的Adapter中的复用问题

大家都知道,我们在使用ListView,GridView时,经常会遇到Adapter复用带来的一些问题,那么,Adapter究竟是怎么复用的呢?今天我们就来一块探究一下。 首先,我们来看一个常见的犹豫复用引起的问题。我们都知道,当我们在ListView中使用复选框时,往往当你勾选第一项时,后边肯定还有一项也会被勾选,对吧?这就是复用带来的问题,接下来我们来分析为什么会出现这个问题。

我们先来看效果图:


我们可以看到,当第0项被选择中的时候,同时第9项也会被选中,这当然不是我们想要的,为什么出现这个问题?不着急,我们先来贴出我们的代码。

下来我们看代码:

item的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <TextView 
        android:id="@+id/id_text"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:layout_centerVertical="true"/>
    <CheckBox 
        android:id="@+id/id_check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"/>

</RelativeLayout>
Adapter中,我们主要看getView方法:

public View getView(final int position, View convertView, ViewGroup parent) {
	ViewHolder holder = null;
	if (convertView == null){
		convertView = mInflater.inflate(R.layout.item_list, parent, false);
		holder = new ViewHolder();
		holder.mTvText = (TextView) convertView.findViewById(R.id.id_text);
		holder.mCb = (CheckBox) convertView.findViewById(R.id.id_check);
		holder.mTvText.setTag(position);
		convertView.setTag(holder);
			
	}else {
		holder = (ViewHolder) convertView.getTag();
	}
		
	holder.mTvText.setText(mData.get(position));
	return convertView;
}
class ViewHolder{
<span style="white-space:pre">	</span>TextView mTvText;
<span style="white-space:pre">	</span>CheckBox mCb;
}

我们一般的Adapter都是这样写,没问题吧。接下来我们来分析:

其实adapter能够复用是因为它会缓存一屏的item的数据,假设我们一屏显示9项(我的模拟器是显示这么多的), 当我们在滑动的过程中,第0项慢慢的划出了屏幕,当完全看不见的时候,adapter会将第0项的view缓存起来,当下一项要进入屏幕时,adapter会先去看缓存中有没有缓存的view,如果有,就直接使用缓存中的view,这个时候,我们getView方法中的convertView就不为空,这个我们会使用convertView.getTag()方法去获取ViewHolder,此时获取的holder使我们第0项创建view时创建的holder,holder中携带的数据都是第0项的数据,也就是说,此时holder.mTvText的值是“第0项”,而holder.mCb是被选择状态,所以我们第9项在复用第0项的convertView时,就会出现其复选框会被选择的问题。

如果你细心,可以发现第9项的holder.mTvText的值是"第9项",并不是“第0项”,你上边不是说数据用的是第0项的,为什么跟上边说的不一样?我擦,你这个骗子!我赶紧跑回去看看代码,是不是哪里写错了,于是发现了这个一句代码:

holder.mTvText.setText(mData.get(position));
看到了吧,就是这货捣的鬼。我们虽然拿到的holder里边都是第0项的数据,但是我们在拿到holder后,会根据当前的position给holder.mTvText重新赋值,这时候mData.get(position)的值就是"第9项",这下终于真相大白了。我们 突然仔细一想,既然TextView可以重新赋值,那么我们给CheckBox也重新赋值,不就可以避免使用第0项的数据导致被选中了吗,对吧? 好像有点道理,接下来我们就试一试。

我们先来分析应该怎么去实现:我们在滑动的过程中,怎么才能知道某一项的CheckBox是不是被选中呢?我这我们使用一个List<Boolean>去记录。当某一项的CheckBox被选择时,我们就根据position将List中相应的位置改为true,当再次点击时,CheckBox又不被选择,我们再改为false,然后在getView中,根据List中的值去设置CheckBox是不是被选中。当然,刚开始进入程序时,所有项的都不会被选中,所有我们初始化List中全部都是false。接下来我们去改getView中的代码:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
	ViewHolder holder = null;
	if (convertView == null){
		convertView = mInflater.inflate(R.layout.item_list, parent, false);
		holder = new ViewHolder();
		holder.mTvText = (TextView) convertView.findViewById(R.id.id_text);
		holder.mCb = (CheckBox) convertView.findViewById(R.id.id_check);
		holder.mTvText.setTag(position);
		convertView.setTag(holder);
			
	}else {
		holder = (ViewHolder) convertView.getTag();
	}
		
	holder.mTvText.setText(mData.get(position));
	//根据mCheckedList中对应位置的值去设置CheckBox
	holder.mCb.setChecked(mCheckedList.get(position));
	holder.mCb.setOnClickListener(new OnClickListener() {
		@Override
		public void onClick(View v) {
			mCheckedList.set(position, !mCheckedList.get(position));
		}
	});	
	return convertView;
}
好了,我们可以看到,我们定义了一个List<Boolean> mCheckList去记录是否被选择,然后监听CheckBox,每次点击,就让当前position中的mCheckList的值取反,在滑动中,我们根据position设置CheckBox的值。下边是在Activity中的调用:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.activity_main);
	mListView = (ListView) findViewById(R.id.id_list);
	mData = new ArrayList<String>();
	initData(15);
	mAdapter = new MyAdapter(this, mData);
	mListView.setAdapter(mAdapter);
}

private void initData(int size) {
	for (int i = 0; i < size; i++) {
		mData.add("第" + i + "项");
	}
}

来看修改后的效果图:


可以看到,我们选中了0,1,2三项,但是9,10,11并没有被选中。到此,我们就分析完了Adapter的复用问题,并解决了CheckBox的问题,有没有心动,赶紧去试试吧~~


注:关于Adapter的缓存原理,请看这篇博客:

http://blog.csdn.net/lmj623565791/article/details/24333277






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Android 开发ListView 是常用的列表控件,而 Adapter 则是 ListView 显示列表数据的适配器。当数据源改变时,我们需要调用 Adapter 的 notifyDataSetChanged() 方法来通知 ListView 更新数据。不过有时候,我们会发现调用 notifyDataSetChanged() 方法后,ListView 并没有更新数据,这通常是由以下几个原因造成的: 1. 数据源没有更新 在调用 notifyDataSetChanged() 方法之前,需要先确保数据源已经更新了。如果数据源没有更新,调用 notifyDataSetChanged() 方法也不会更新 ListView 显示的数据。 2. Adapter 对象没有重新设置 如果使用的是同一个 Adapter 对象,那么需要重新设置 Adapter 对象才能更新 ListView 显示的数据。可以通过 setAdapter() 方法重新设置 Adapter 对象。 3. ListView 没有重新绘制 当调用 notifyDataSetChanged() 方法后,ListView 并不会立即重新绘制,而是等到系统认为需要重新绘制时才会更新。可以通过调用 invalidate() 方法让 ListView 立即重新绘制。 4. 数据源和 Adapter 对象不匹配 如果数据源和 Adapter 对象不匹配,即数据源的数据类型和 Adapter 的数据类型不一致,调用 notifyDataSetChanged() 方法也无法更新 ListView 显示的数据。 综上所述,如果在 ListView 调用 notifyDataSetChanged() 方法无效,可以先检查数据源是否更新,是否重新设置 Adapter 对象,是否调用了 invalidate() 方法以及数据源和 Adapter 对象是否匹配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值