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

[size=medium] 鄙人最近刚开始学习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之间数据的差异。[/size]
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开发,如果各位看官有更好的解决方案,请一定告知我
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值