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开发,如果各位看官有更好的解决方案,请一定告知我
Android开发中ListView多屏的全选、反选功能相关推荐
- php 全选 反选,利用vue实现全选反选功能
这次给大家带来利用vue实现全选反选功能,利用vue实现全选反选功能的注意事项有哪些,下面就是实战案例,一起来看一下. 全选功能可以说是前端开发中非常常见的一个功能,以前的项目开发用jQuery开发比 ...
- angularjscheckbox全选_AngularJS实现全选反选功能
这篇文章主要介绍了AngularJS实现全选反选功能,这里用到AngularJS四大特性之二----双向数据绑定,对angularjs实现全选反选相关知识感兴趣的朋友一起学习吧 AngularJS是为 ...
- JS 原生实现复选框全选反选功能
** JS 原生实现复选框全选反选功能 ** 按钮功能实现思路: 全选按钮: 直接将全选按钮的状态赋值给每一个 复选框. 复选框:只有当所有的复选框选中时,全选按钮才能选中,所以每当复选框每点击一次就 ...
- java做全选反选功能_[Java教程]js实现全选反选
[Java教程]js实现全选反选 0 2017-04-04 00:00:12 在前端中用到全选反选的案例并不少,在这里呢我就实现这个功能给大家参考参考. 这里呢就先贴上我的html和css代码 快递 ...
- android 中自定义安装,Android开发中ListView自定义adapter的封装
[引入] 我们一般编写listView的时候顺序是这样的: •需要展示的数据集List •为这个数据集编写一个ListView •为这个ListView编写一个Adapter,一般继承自BaseAda ...
- element中checkbox全选反选功能
单循环: <el-button @click="handleLimit(scope.row)"type="warning"icon="el-ic ...
- Javascript实现复选框(全选反选功能)
在一个项目开发中我们经常见到页面会有复选框的全选和反选功能,这样我们才能够进行下一步的批量删除或者实现其他的功能,那我们应该怎样去实现这样的一个功能呢?其实想要实现复选框的全选和反选功能很简单只要得到 ...
- 功能选中jquery实现全选反选功能
时间紧张,先记一笔,后续优化与完善. 全选功能 $(function () { $('#allchecked').click(function () { ...
- 直播软件app开发中直播公屏如何做出来?
直播软件app开发中直播公屏如何做出来? 功能 直播软件app开发中公屏最简单的就是通过一个可滑动的列表进行展示用户发送出来的消息,当然,一般都是通过服务器给客户端推送单条或者一组数据,然后客户端再把 ...
最新文章
- 如何将空目录添加到Git存储库?
- java 的23种设计模式 之装B者模式
- 带着问题学 Kubernetes 架构!
- 737 页《吴恩达深度学习核心笔记》发布,黄海广博士整理!
- android SQLite查询并显示用户输入的选择信息
- linux入门 适合初学者_【推荐】适合初学者临摹的国画|国画基础入门教学视频教程!...
- xhell启动mysql_xshell怎么搭建mysql
- Linux查看依赖属于哪个软件包
- python在生物中的应用_关于举办“Python编程语言在生物科学研究中的应用”学术报告的通知...
- 移动端分享链接给微信好友
- 2022 阿里 java 面经
- ANSYS模态分析详细步骤记录
- 图书馆数据可视化大屏制作教程
- STM32F4 GPIO模式及工作原理详解
- vivado sata ip (phy)配置
- 什么是网站被黑了?网站被黑客攻击怎么办?
- 阿里云CDN是什么意思?
- SylixOS这个操作系统怎么样?
- 月结重头戏--外币评估
- 抖音如何进行广告的投放
热门文章
- 判断素数的五种方法最全埃筛素数欧筛素数孪生素数
- CAN FD如何应用Vector诊断工具链?
- stata中计算公式命令_Stata数据处理:各种求和方式一览
- java函数式编程-科里化
- 纪念一下我那块分区表坏了的60G硬盘
- 龙贝格算法求解椭球周长
- Please add following line to .bashrc: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/username/.muj
- 闲置遥控器升级改造,《豪杰遥控器》。
- 华为OD机试 -众数和中位数(Java) | 机试题+算法思路+考点+代码解析 【2023】
- 微信支付 php简单源码,php微信支付相关源码