Android开发中ListView多屏的全选、反选功能

鄙人最近刚开始学习Android,在练习的时候写到一个ListView的全选反选功能。本来以为这个功能很简单,随随便便就能搞定,结果真的下手去做的时候被虐的死去活来,不知道为什么在做全选和反选的操作是,总有几个选项在意料之外被选上或者取消,导致每次反选的结果都不是我预期的结果。而且在全选或者反选之后再去点checkbox,会出现操作无效的情况,一滚动屏幕,选项就又回到全选的状态了。

后来问了一些以前开发过Android的人才知道,原来Android里面的ListView是基于这样一种工作原理:当数据超过一屏的时候,每次滚动都会重新加载ListView里面每一行的View。而新加进来的View实际上是之前的View重新加载。这样ListView就只需要加载一个屏幕所展示数量的View就可以了,但是副作用就是消失到屏幕外的View系统不会为你保存。举个例子,一屏能显示7行数据,要显示的数据总共有10行。那头一屏必然是0~6,当滚到第二屏的时候,就是6、7、8、9、0、1。头几个被滚出屏幕的View又重新被加载了回来。我之前用的方法是通过for循环获取ListView里面的子元素然后改变CheckBox的状态,由于刚才那个原因,总是存在序号错位的问题,所以才会总是出现刚才说的全选和反选时的诡异现象。

所以解决办法除了要把当前屏幕内的checkBox的状态按照原来数据的序号进行改变以外,还要同时修改adapter里面的数据,以确保滚屏的时候数据一致。我在看书的时候基本上所有的例子都是把adapter写成一个匿名内部类,这样adapter的数据一定与activity里面的数据是同步的。但是如果adapter里面要实现很多功能的话,就会使activity这个类过于庞大,违反了“指责单一化原则”。而如果把Adapter单独写出来,数据同步就不太好处理。

    所以我抽离了一个中间的专门管理数据的类InAccountListManager(InAccount是我这个工程的某个bean,可以忽略不计),来同步activity与adapter之间数据的差异。
package com.accountms.activity.util;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.app.Activity;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ListView;

import com.accountms.activity.R;
import com.accountms.databean.BaseBean;
import com.accountms.databean.InAccountBean;
import com.accountms.functioninterface.IListHandler;
import com.example.util.IBaseInfo;

/**
 * ListView数据管理类
 * 
 * @author Treagzhao 用来同步Activity与Adapter之间的数据,同时管理正向同步和反向同步的互斥。
 * 
 */
public class InAccountListManager implements IBaseInfo, IListAdapter,
		IListActivity {
	private IListActivity activity;
	private IListAdapter adapter;
	// list用来保存要展示的数据(InAccountBean是我要操作的bean类,可以换成任意数据)
	private List<InAccountBean> list;
	// selectedList 用来保存已经选中的数据
	private Set<InAccountBean> selectedList = new HashSet();
	// posList与selectedList数据一致,用以记录所保存的数据在源数据中的位置
	private Set<Integer> posList = new HashSet();
	// 如果正在执行“全选”或者“反选”操作时,用于取消checkbox的onCheckedChagne事件(下面会有详细说明)
	private boolean operatingMutex = false;
	private ListView listView;

	/**
	 * 
	 * @param list
	 *            源数据
	 * @param activity
	 *            具体的activity
	 * @param adapter
	 *            listView的adapter
	 * @param listView
	 *            展示数据的listView
	 */
	public InAccountListManager(List<InAccountBean> list,
			IListActivity activity, IListAdapter adapter, ListView listView) {
		this.activity = activity;
		this.adapter = adapter;
		this.list = list;
		this.listView = listView;
	}

	public Set<InAccountBean> getSelectedList() {
		return selectedList;
	}

	public void setSelectedList(Set<InAccountBean> selectedList) {
		this.selectedList = selectedList;
	}

	public Set<Integer> getPosList() {
		return posList;
	}

	public void setPosList(Set<Integer> posList) {
		this.posList = posList;
	}

	/**
	 * 继承IListAdapter接口的方法,用以处理用户点击“全选”后的操作
	 */
	@Override
	public void onSelectedAll() {
		// “全选”
		// 是一个activity向adapter单向同步数据的操作,可是在执行checkbox.setChecked时,会触发checkbox的onCheckedChange事件,会把adapter的数据重新发回来,造成干扰。所以要设定一个标记量,activity向adapter单向同步的时候,不接受反向的同步。
		operatingMutex = true;
		// 将所有数据都放在已选数据中
		for (int i = 0; i < list.size(); i++) {
			InAccountBean bean = list.get(i);
			selectedList.add(bean);
			posList.add(i);
		}
		// 改变checkbox的状态
		for (int i = 0; i < listView.getChildCount(); i++) {
			LinearLayout linear = (LinearLayout) listView.getChildAt(i);
			CheckBox checkbox = (CheckBox) linear
					.findViewById(R.id.inaccount_list_checkbox);
			checkbox.setChecked(true);
		}
		this.adapter.onSelectedAll();
		operatingMutex = false;
	}

	@Override
	public void onSelectedAllCancle() {
		// TODO Auto-generated method stub

	}

	/**
	 * 继承自IListActivity的方法,用以处理checkbox的状态点击事件
	 */
	@Override
	public void onItemCheckedChanged(int pos, boolean checked) {
		//如果是“全选”或者“反选”类的操作则退出,只执行用户点击checkbox这一类的adapter到activity的数据同步
		if (operatingMutex)
			return;
		if (checked) {
			selectedList.add(list.get(pos));
			posList.add(pos);
		} else {
			selectedList.remove(list.get(pos));
			posList.remove(pos);
		}
		this.activity.onItemCheckedChanged(pos, checked);
	}

	/**
	 * 继承自IListAdapter的方法,用以处理用户点击“反选”的操作
	 */
	@Override
	public void onReverseSelected() {
		//原理同上
		operatingMutex = true;
		//将选中的数据逆置
		for (int i = 0; i < list.size(); i++) {
			InAccountBean bean = list.get(i);
			if (selectedList.contains(bean)) {
				selectedList.remove(bean);
				posList.remove(i);
			} else {
				selectedList.add(bean);
				posList.add(i);
			}
		}
		//设定checkbox的状态
		for (int i = 0; i < listView.getChildCount(); i++) {
			LinearLayout linear = (LinearLayout) listView.getChildAt(i);
			CheckBox checkbox = (CheckBox) linear
					.findViewById(R.id.inaccount_list_checkbox);
			//注意这里获取的是checkbox的tag,不是最外层的linear的tag,具体的原理在adapter类里面会提到
			int pos = (Integer) checkbox.getTag();
			if (posList.contains(pos))
				checkbox.setChecked(true);
			else
				checkbox.setChecked(false);
		}
		operatingMutex = false;
	}
}
    这里面用到两个接口IListActivity和IListAdapter,前者的作用是接受adapter发出的onCheckedChange事件,后者是响应“全选”和“反选”。由于这个Manager类相当于activity和adapter之间的中间件,它的作用是把activity的操作指令预处理后传递给adapter,反之亦然,所以Manager类同时实现这两个接口。
package com.accountms.activity.util;

public interface IListActivity {
	public void onItemCheckedChanged(int pos, boolean checked);
}
package com.accountms.activity.util;

public interface IListAdapter {
	public void onSelectedAll();

	public void onSelectedAllCancle();

	public void onReverseSelected();
}
    以下是Activity的代码
package com.accountms.activity;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.accountms.activity.util.IListActivity;
import com.accountms.activity.util.InAccountListManager;
import com.accountms.adapter.InAccountListAdapter;
import com.accountms.databean.InAccountBean;
import com.accountms.functioninterface.IThreadHandler;
import com.accountms.thread.InAccountThreadFactory;
import com.example.util.BaseActivity;
import com.example.util.IBaseInfo;
import android.os.Bundle;
import android.app.ProgressDialog;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

public class InAccountListActivity extends BaseActivity implements IBaseInfo,
		IThreadHandler, IListActivity {
	
	private TextView selectAll, reverseSelect, removeBtn;
	private ListView listView;
	private Button addBtn;
	private int page = 1;
	private ProgressDialog dialog;
	//用以保存从数据库中读出的信息
	private List<InAccountBean> list;
	//“哒啦啦!”主角出现了
	private InAccountListManager manager;
	//listView 的adapter
	private InAccountListAdapter adapter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_in_account_list);

		init();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.in_account_list, menu);
		return true;
	}

	/**
	 * 初始化View
	 */
	@Override
	protected void findView() {
		listView = (ListView) findViewById(R.id.info_list);
		selectAll = (TextView) findViewById(R.id.text_selecteAll);
		reverseSelect = (TextView) findViewById(R.id.text_reverseSelect);
		removeBtn = (TextView) findViewById(R.id.text_remove);
	}

	/**
	 * 绑定事件
	 */
	@Override
	protected void bindListener() {
		selectAll.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View view) {
				if (manager == null)
					return;
				manager.onSelectedAll();
			}
		});
		reverseSelect.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View view) {
				manager.onReverseSelected();
			}
		});
	}

	/**
	 * 初始化各个功能
	 */
	@Override
	protected void initPlugins() {
		initList();
	}

	/**
	 * 初始化数据List,新建一个线程从数据库中获取数据
	 */
	private void initList() {
		dialog = new ProgressDialog(this);
		dialog.setTitle("提醒");
		dialog.setProgress(0);
		dialog.setMax(100);
		dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		dialog.setMessage("读取中····");
		dialog.setCancelable(false);
		dialog.setIndeterminate(false);
		dialog.show();
		Map map = new HashMap();
		map.put("page", page);
		Thread thread = new Thread(InAccountThreadFactory.getThread(this,
				InAccountThreadFactory.TYPE_LIST, map, this, dialog));
		thread.start();
	}

	/**
	 * 获取数据库的线程回调函数,本文所说的内容基本就是从这里开始的
	 */
	@Override
	public void handle(Map map) {
		list = (List<InAccountBean>) map.get("list");
		adapter = new InAccountListAdapter(InAccountListActivity.this, list);
		manager = new InAccountListManager(list, this, adapter, listView);
		adapter.setManager(manager);
		listView.setAdapter(adapter);
		if (dialog != null)
			dialog.cancel();
	}

	@Override
	public void onItemCheckedChanged(int pos, boolean checked) {
		// TODO 做爱做的事
	}

}

接下来是adapter的代码

package com.accountms.adapter;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Set;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.accountms.activity.R;
import com.accountms.activity.util.IListAdapter;
import com.accountms.activity.util.InAccountListManager;
import com.accountms.databean.InAccountBean;
import com.example.util.IBaseInfo;

/**
 * 
 * @author Treagzhao 设定ListView的Adapter
 * 
 */
public class InAccountListAdapter extends BaseAdapter implements IBaseInfo,
		IListAdapter {
	private List<InAccountBean> list;
	public Activity activity;
	private SimpleDateFormat format;
	// 我们的主角在这里
	private InAccountListManager manager;

	public InAccountListManager getManager() {
		return manager;
	}

	public void setManager(InAccountListManager manager) {
		this.manager = manager;
	}

	public InAccountListAdapter(Activity activity, List<InAccountBean> list) {
		this.list = list;
		this.activity = activity;
		format = new SimpleDateFormat("yyyy-MM-dd");
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return list.size();
	}

	@Override
	public Object getItem(int pos) {
		// TODO Auto-generated method stub
		return pos;
	}

	@Override
	public long getItemId(int pos) {
		// TODO Auto-generated method stub
		return pos;
	}

	/**
	 * 看这里看这里看这里
	 * 
	 * @deprecated 这个函数两个情况下会被调用,除了一开始的初始化以外,每次listView滚动时也会调用这个函数。
	 */
	@Override
	public View getView(int pos, View convertView, ViewGroup arg2) {
		LayoutInflater inflater = this.activity.getLayoutInflater();
		LinearLayout linear = null;

		if (convertView == null) {
			linear = (LinearLayout) inflater.inflate(
					R.layout.inaccountlist_layout, null);
			linear.setTag(pos);
		} else {
			linear = (LinearLayout) convertView;
		}
		// 注意这个地方,除了LinearLayout的初始化以外,其内部的所有子View在所有情况下均被执行。
		CheckBox checkbox = (CheckBox) linear
				.findViewById(R.id.inaccount_list_checkbox);
		// 由于checkbox的tag是每次随着list的顺序设定的,所以checkbox的tag一定是与数据一致的
		checkbox.setTag(pos);
		TextView mark = (TextView) linear
				.findViewById(R.id.text_inaccount_mark);
		TextView time = (TextView) linear
				.findViewById(R.id.text_inaccount_time);
		mark.setText(list.get(pos).getMark());
		time.setText(format.format(list.get(pos).getTime()));
		// 获取已经被选中的数据,由于这个数据是每次加载时从manager对象获取,所以一定是同步的数据
		Set<Integer> posList = manager.getPosList();
		if (posList.contains(pos)) {
			checkbox.setChecked(true);
		} else {
			checkbox.setChecked(false);
		}
		checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				int pos = (Integer) buttonView.getTag();
				// 触发manager的onItemCheckedChanged事件
				manager.onItemCheckedChanged(pos, isChecked);
			}
		});
		return linear;
	}

	@Override
	public void onSelectedAll() {
	}

	@Override
	public void onSelectedAllCancle() {
	}

	@Override
	public void onReverseSelected() {
	}

}

由于activity和adapter的操作都是对manager的操作,而且获取数据是都是从manager里面获取,所以双方的数据一定是一致的。而且有manager在中间进行管理,就会让activity的操作不会返回来对数据造成影响。同时因为有manager的存在,大大减少了activity和adapter的工作量。解决了ListView在超过一屏是的全选和反选功能。

鄙人刚刚开始学习Android开发,如果各位看官有更好的解决方案,请一定告知我

相关推荐