项目中需要使用一个多层级关系的ListView,问了下需求,回答是无限级。。。好吧,参考了网上的各种资源,似乎对无限级这种都没有很好的解决方案,特别是在后台给我的数据仅仅告诉我上一级的名称,大致就是{“私有属性”,”上一级菜单名称”},偶尔看到一个开发人员的一个提示,所谓的展开就是增加item,关闭就是删除item,顿时有了一个极其笨的办法。既然是最没有技术含量的,所以这里也会有一些问题,最严重的问题就是——性能低下,如果处理数量大的,还是另辟蹊径吧。
闲话少说,开始正文:
1.数据bean类 MenuData.class
private String name; // 本级菜单的名字
private String text; // 服务器传来要展示的数据,可以是任意数据形式
private String top_menu; // 上级菜单名称,无论返回上下级菜单关系,原理是一样的
private boolean isNext; // 是否有下一级菜单
private boolean isOpen; // 菜单是否展开
private int level; // 所在层级
//setter getter...
这边后台太弱,我这边的层级和是否有下一级菜单,都是我自己定义和计算出来的,如果后台强大,完全没必要这个步骤。
黑体加粗强调下,得到的数据先一律将isOpen设为false,因为都是关闭的,这个步骤我是在获取数据的地方设置的
2.整理服务器得来的数据
// 整理从服务器获取的数据
private void initData(List<MenuData> list) {
// 得到第一级菜单
List<MenuData> menuList = new ArrayList<>();
menuList.clear(); // 习惯问题,清一下,就跟刷新一样
for (MenuData data : list) {
// 我这边第一级菜单服务器返回的上级菜单是"null",对,没看错是字符串"null"
if ("null".equals(data.getTop_menu())) {
// 设置层级
data.setLevel(1);
// 寻找是否有下级菜单
for (MenuData data1 : list) {
if (data.getName().equals(data1.getTop_menu())) {
// 存在即说明有
data.setNext(true);
break;
} else {
// 一直没有那就是没有咯
data.setNext(false);
}
}
// 把一级菜单都加到菜单列表中
menuList.add(data);
}
}
// 这里创建一个菜单map,key是菜单的名字,value是该菜单名字下的下一级菜单集合
menuMap = new HashMap<>();
menuMap.put("null", menuList);
// 差不多就可以可以感受到没有技术含量的代码了,两次for循环完了,再来三次
for (MenuData data : list) {
String name = data.getName();
// 一定要在new一次,也就是一个新的对象,而不是clear,不然。。。你试试
menuList = new ArrayList<>();
for (int j = 0; j < list.size(); j++) {
if (name.equals(list.get(j).getTop_menu())) {
for (MenuData data1 : list) {
if (data.getName().equals(data1.getTop_menu())) {
data.setNext(true);
break;
} else {
data.setNext(false);
}
}
menuList.add(list.get(j));
}
}
menuMap.put(name, menuList);
}
// 此处的mList是与ListViw绑定的list,也就是负责数据更新的,此处是让页面进入即显示第一级菜单
mList.clear();
mList.addAll(menuMap.get("null"));
mAdapter.notifyDataSetChanged();
}
看到没有,如果后台够强大,直接返回层级和是否有下级菜单,将省下很多事情,什么你看到我mList,mAdapter都没定义,别闹,这个你应该会啊,来给你完整代码
mListView = (ListView) v.findViewById(R.id.list_view);
mList = new ArrayList<>();
mAdapter = new MenuAdapter(mContext, mList);
mListView.setAdapter(mAdapter);
3.adapter中的处理
什么叫史上最没有技术含量,那就是,没有什么特别要求,你以前怎么写的,现在怎么写就行了,完全一样。好吧,我有一点小要求,比如缩进,比如打开和关闭的图标不同,背景颜色要不一样。给关键代码吧
// 背景颜色,因为我的是白色文字,所以这四种颜色比较搭,各位自行选择啊
private int[] colors = {0xFF000000, 0xFF0033CC, 0xFF01CC00, 0xFFFF3300};
... 此处省略各种写adapter就要重复的代码
MenuData data = mList.get(position);
vh.tx.setText(data.getText());
vh.name.setText(data.getName());
// 把图标进行缩进,因为我的图标是在最前面,自行选择哪个控件缩进
vh.icon.setPadding((data.getLevel() - 1) * 8, 8, 8, 8);
vh.layout.setBackgroundColor(colors[data.getLevel() % 4]);
// 判断是否有下级菜单
if (!data.isNext()) {
// 没有下级菜单显示圆圈
vh.icon.setImageResource(R.mipmap.knowledgetree_leaf);
} else if (data.isOpen()) {
// 有下级菜单,且为展开状态
vh.icon.setImageResource(R.mipmap.knowledgetree_rootexpanded);
} else {
// 有下级菜单,且为关闭状态
vh.icon.setImageResource(R.mipmap.knowledgetree_rootunexpanded);
}
我比较大方,送你三张图片了
还有什么事要做了?最重要的,展开和关闭状态要实现啊,不然要写这个干吗啊。。。
各位要想按图标进行展开和关闭了,就在adapter里面设置,嗯,会吧?我是整个item点击有效,所以我就是在页面的类里面写的了。
如果要在adapter里面写,给个思路吧,设置监听的时候,把position带上就ok了。
下面那个是我在这个项目中基本类似需求的代码,我就不在这说了。
class Click implements View.OnClickListener {
int position;
public Click(int position) {
this.position = position;
}
@Override
public void onClick(View v) {
Data data = mList.get(position);
switch (v.getId()) {
case R.id.btn1:
break;
case R.id.btn2:
break;
}
}
}
4.数据更新
private class ItemClick implements android.widget.AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
MenuData menuData = mList.get(position);
// 因为要对数据更新,所以先把数据暂存下来
List<MenuData> tempList = new ArrayList<>();
tempList.addAll(mList);
if (!menuData.isOpen() && menuData.isNext()) {
// 记录该菜单是展开状态
menuData.setOpen(true);
mList.clear();
// 又到体现最没技术含量的时刻到了,简单点说就是,把点击项的前面原样放入list,把点击的那项下一级菜单加进去,再把原来的后部分数据加进去
for (int tempI = 0; tempI <= position; tempI++) {
mList.add(tempList.get(tempI));
}
// 获得点击的下级菜单,并插入
for (MenuData data : menuMap.get(menuData.getName())) {
// 服务器没有返回层级,所以自己计算层级,很容易理解
data.setLevel(menuData.getLevel() + 1);
// 这里的作用是再怎么没有技术含量也要适当考虑性能,计算目前展开的最大层级,方便下面的关闭菜单,下面会讲到
if (menuData.getLevel() + 1 > maxOpen) {
maxOpen = menuData.getLevel() + 1;
}
mList.add(data);
}
for (int tempI = position + 1; tempI < tempList.size(); tempI++) {
mList.add(tempList.get(tempI));
}
// 这里就要进行关闭菜单了
} else if (menuData.isOpen() && menuData.isNext()) {
menuData.setOpen(false);
String delName = menuData.getName();
// 这里要注意,与展开不同,展开只要插入下一级菜单,而关闭是要将下一级及以下包含的各级全部移除,这里知道我上面要定义展开最大层级的作用了吧
for (int i = 0; i < tempList.size(); i++) {
MenuData delData = tempList.get(i);
String delTopName = delData.getTop_menu();
for (int tempI = menuData.getLevel(); tempI < maxOpen; tempI++) {
if (delTopName.equals(delName)) {
delData.setOpen(false);
mList.remove(delData);
} else {
for (int j = 0; j < tempList.size(); j++) {
if (tempList.get(j).getName().equals(delTopName)) {
delTopName = tempList.get(j).getTop_menu();
}
}
}
}
}
}
mAdapter.notifyDataSetChanged();
}
}
因为没有技术含量,所以有点难以理解,主要是要理解核心思想:展开就是增加,关闭就是删除,以上代码就好理解了。
因为这是实际商业项目中的内容,所以我改了一些变量名字,而且不是在开发工具中,是在该博客中写的,所以可能会有误差,希望各位理解核心思想,仅供参考,copy代码极易出错。However,我在周末一定把能用的demo放上。哈哈。。。能copy还是copy吧。
遇到的问题:这只能说是一个替代方案,这里的无限级虽然可以实现,但是效率很低,其次,如果考虑每一级都有缩进的话,会发现item占满全屏了,那么显示就会很乱,这里就需要左右滑动的效果,我还没有实现,如果有解决方案的希望一起探讨。
附上demo地址
demo地址