Android 绘制多级树形选择列表实例代码

一、概述

前段时间有个项目的需要在Android端显示一个复选的多层树形控件,主要展示一个公司的组织架构,类似总部下面有各个部门,部门之下是组和员工等。另外需要加上展开与回收部门详情、关闭部分已开展的布局、勾选等功能。

效果图如下:

二、思路分析

毫无疑问,对于这种数据可能达到几千几万行的列表视图,我们需要选择recyclerview等具有回收item功能的控件,因此Item的状态保持放在Model中而不是View中。

由于原始数据是树形结构的,我们需要先将树形结构转换为列表数据,类似根结点 - 父节点1 - 子结点1 - 子节点2 - 父节点2......这种形式 - 这恰恰是树的前序遍历

实现思路 - 为了更简洁明白,左右颠倒处理

三、具体实现

简单的节点实现

public abstract class SimpleTreeNode {
//层级
protected int hierarchy;
//父节点
protected K parent = null;
//子节点
protected final List<K> children = new ArrayList<>();
protected boolean isSelected;  // 是否被选中
protected boolean isExpand;   // 是否展开
}
前序遍历则发生在adapter的getItem和getItemCount的时候
public T getItem(int position) {
  int[] cur = {position};
  return getNode(topGroups, cur);
}
/**
 * 先序遍历 - 获取指定位置的节点
 *
 * @param nodes  nodes
 * @param position itemPosition 数组只是为了实现手动box实现共享position
 * @return MultiSelectNode or null
 */
 protected T getNode(List<T> nodes, final int[] position) {
  for (T node : nodes) {
    if (position[0] == 0) {
      return node;
    }
    position[0]--;
    if (node.getChildren().size() > 0) {
      T finalNode = getNode(node.getChildren(), position);
      if (finalNode != null) {
        return finalNode;
      }
    }
  }
  return null;
}
 /**
 * 先序遍历 - 获取展示的总长度 (isExpand = true)
 *
 * @param nodes nodes
 * @return int
 */
protected int getTreeSize(List<T> nodes) {
  int size = 0;
  for (T node : nodes) {
    size++;
    size += getTreeSize(node.getChildren());
    }
  return size;
}

对于如何实现展开和收缩的功能,我尝试了两种方式:

在渲染item的时候判断node.isExpand = false时,对item进行Gone处理,实际处理发现列表卡顿非常严重:假设所有的item都是隐藏的,那么因为列表没有显示全,所有的item都会进行渲染一遍....

数据遍历的时候将非展开的数据过滤掉:这种方式完美可行,只需要修改下遍历方法即可

protected int getTreeSize(List<T> nodes) {
 int size = 0;
 for (T node : nodes) {
   size++;
   // 展开过滤
   if (node.isExpand()) {
     size += getTreeSize(node.getChildren());
   }
 }
 return size;
 }
 protected T getNode(List<T> nodes, final int[] position) {
 for (T node : nodes) {
   if (position[0] == 0) {
     return node;
   }
   position[0]--;
   // 展开过滤
   if (node.isExpand() && node.getChildren().size() > 0) {
     T finalNode = getNode(node.getChildren(), position);
     if (finalNode != null) {
       return finalNode;
     }
   }
 }
 return null;
 }

以上多级树形列表的展开与隐藏便完成了,剩下的便是对树节点的一些操作:例如一个item展开的时候对其他同级item隐藏;一个item被勾选或取消勾选的时候改变其父节点和子节点的状态等。对于这些操作,我采用了类似Motion Event的方式 - 用事件传递与分发来处理。

比如展开的时候同级的item隐藏,其实便是通知兄弟节点设置expand为false。

通知兄弟节点

勾选的操作稍麻烦,可能需要递归通知父节点检查更新,以及递归通知子节点勾选操作,取消勾选亦如此。

关键代码如下

/**
 * Class: SimpleTreeNode
 * Author: zwgg
 * Date: 2017/10/16
 * Time: 10:35
 * 简单的树节点模板类
 * 这个自限定泛型可能有点费解:用于以基类导出类作为自身的泛型,以实现模板功能
 * 例如:ClassNameA extend SimpleTreeNode< ClassNameA , T >
 * @see Enum
 */
 public abstract class SimpleTreeNode<K extends SimpleTreeNode<K, T>, T extends TreeNodeEvent> {
  //层级
  protected int hierarchy;
  //父节点
  protected K parent = null;
  //子节点
  protected final List<K> children = new ArrayList<>();
  public SimpleTreeNode() {
  }
  public SimpleTreeNode(int hierarchy) {
    this.hierarchy = hierarchy;
  }
  public void bindingParent(K parent) {
    this.parent = parent;
  }
  public void bindingChild(K child) {
    this.children.add(child);
  }
  public void bindingChildren(List<K> children) {
    this.children.clear();
    this.children.addAll(children);
  }
  public void dataBinding(K parent, K child) {
    parent.bindingChild(child);
    child.bindingParent(parent);
  }
  public int getHierarchy() {
    return hierarchy;
  }
  public void setHierarchy(int hierarchy) {
    this.hierarchy = hierarchy;
  }
  /**
   * 通知父节点
   * @param event event
   */
  public void notifyParent(T event) {
    if (parent != null) {
      event.setNotifyType(TreeNodeEvent.NOTIFY_PARENT);
      parent.onEvent(event);
    }
  }
  /**
   * 通知子节点
   * @param event event
   */
  public void notifyChildren(T event) {
    event.setNotifyType(TreeNodeEvent.NOTIFY_CHILDREN);
    for (K child : children) {
      child.onEvent(event);
    }
  }
  /**
   * 通知兄弟节点 - 需要具有相同的Parent
   * @param event event
   */
  public void notifyBrother(T event) {
    if (parent != null) {
      event.setNotifyType(TreeNodeEvent.NOTIFY_BROTHER);
      for (K child : parent.children) {
        if (child != this) {
          child.onEvent(event);
        }
      }
    }
  }
  public abstract void onEvent(T event);
  public List<K> getChildren() {
    return children;
  }
  }

业务节点

public class MultiSelectNode<T extends MultiSelectNode<T>> extends SimpleTreeNode<T, MultiSelectEvent> {
  private boolean isSelected;  // 是否被选中
  private boolean isExpand;   // 是否展开
  /**
   * @param hierarchy view层级 - 用于产生viewType
   */
  public MultiSelectNode(int hierarchy) {
    super(hierarchy);
  }
  /**
   * 事件处理方法
   *
   * @param event 传递得到的事件
   */
  @Override
  public void onEvent(MultiSelectEvent event) {
    switch (event.getNotifyType()) {
      case TreeNodeEvent.NOTIFY_CHILDREN:
        // 父节点通知子节点改变选择状态
        if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) {
          // 如果子节点选择状态有变,则继续通知下层节点改变状态
          if (event.isSelected() != isSelected()) {
            setSelected(event.isSelected());
            notifyChildren(event);
          }
        }
        break;
      case TreeNodeEvent.NOTIFY_PARENT:
        // 子节点选择状态更改,则通知父节点重新根据所有子节点设置自身状态
        if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) {
          if (recheckSelected() != isSelected()) {
            setSelected(!isSelected());
            // 如果父节点有变,则继续递归通知
            notifyParent(event);
          }
        }
        break;
      case TreeNodeEvent.NOTIFY_BROTHER:
        // 通知兄弟节点改变扩展状态
        if (event.getEventType() == MultiSelectEvent.EVENT_SET_EXPAND) {
          if (event.isExpand() != isExpand()) {
            setExpand(event.isExpand());
          }
        }
        break;
      default:
        break;
    }
  }
  /**
   * 关闭兄弟节点扩展
   *
   * @param isExpand 是否扩展
   */
  public void setOtherGroupsExpand(boolean isExpand) {
    MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_EXPAND);
    event.setExpand(isExpand);
    notifyBrother(event);
  }
  /**
   * 通知父节点根据子节点设置状态
   * 注:选择具有递归性,如果父类状态有变会继续通知父类
   */
  public void setParentRecheckSelected() {
    MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED);
    notifyParent(event);
  }
  /**
   * 通知子节点设置选择状态
   * 注:选择具有递归性,会设置所有孩子以及孩子的孩子状态
   *
   * @param isSelected 是否选择
   */
  public void setChildrenSelected(boolean isSelected) {
    MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED);
    event.setSelected(isSelected);
    notifyChildren(event);
  }
  /**
   * 根据子节点设置自身状态
   *
   * @return isSelected boolean
   */
  public boolean recheckSelected() {
    for (MultiSelectNode child : children) {
      if (!child.isSelected()) {
        return false;
      }
    }
    return true;
  }
  public boolean isExpand() {
    return isExpand;
  }
  public void setExpand(boolean expand) {
    isExpand = expand;
  }
  public boolean isSelected() {
    return isSelected;
  }
  public void setSelected(boolean selected) {
    isSelected = selected;
  }
  }

Event事件

public class TreeNodeEvent {
  public static final int NOTIFY_PARENT = 1;
  public static final int NOTIFY_CHILDREN = 2;
  public static final int NOTIFY_BROTHER = 3;
  private int notifyType;
  public TreeNodeEvent(){
  }
  public TreeNodeEvent(int notifyType) {
    this.notifyType = notifyType;
  }
  public int getNotifyType() {
    return notifyType;
  }
  public void setNotifyType(int notifyType) {
    this.notifyType = notifyType;
  }
}
public class MultiSelectEvent extends TreeNodeEvent {
  public static final int EVENT_SET_SELECTED = 1;
  public static final int EVENT_SET_EXPAND = 2;
  //事件类型
  private int eventType;
  private boolean isSelected;
  private boolean isExpand;
}

详细可见Github: https://github.com/zwgg/MultiSelectList

总结

以上所述是小编给大家介绍的Android 绘制多级树形选择列表实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • Android UI 之实现多级树形列表TreeView示例
  • Android多级树形列表控件
(0)

相关推荐

  • Android多级树形列表控件

    我们开发app过程中,经常会碰到需要 多级列表展示的效果.而Android原生sdk中根本没有3级 4级甚至更多级别的列表控件. 所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展. android中有ExpandListView控件,但是这个控件只支持两级列表.对于多级列表如果重写这个不是很好用. 实现这种列表 思想就是递归,构造一个子父级的关系. 话不多说 代码中体会 Activity package com.example.c

  • Android UI 之实现多级树形列表TreeView示例

    所谓TreeView就是在Windows中常见的多级列表树,在Android中系统只默认提供了ListView和ExpandableListView两种列表,最多只支持到二级列表的实现,所以如果想要实现三级和更多层次的列表,就需要我们自己来做一些处理了. 其实这个效果很久以前就有人想办法实现了,但是实现的效果有一些问题,我的实现思路主要也是来自于网络,但是在其基础上修正了逻辑上的一些错误,做了一些优化. 先来看一下效果: 然后大体说一下思路: 其实这里实现的多级列表只是一个视觉效果,我们看到的分

  • Android 绘制多级树形选择列表实例代码

    一.概述 前段时间有个项目的需要在Android端显示一个复选的多层树形控件,主要展示一个公司的组织架构,类似总部下面有各个部门,部门之下是组和员工等.另外需要加上展开与回收部门详情.关闭部分已开展的布局.勾选等功能. 效果图如下: 二.思路分析 毫无疑问,对于这种数据可能达到几千几万行的列表视图,我们需要选择recyclerview等具有回收item功能的控件,因此Item的状态保持放在Model中而不是View中. 由于原始数据是树形结构的,我们需要先将树形结构转换为列表数据,类似根结点 -

  • Android实现多级树形选择列表

    项目中有多个地方要用到多级列表的菜单,最开始我用的是ExpandableListView,但问题是ExpandableListView只支持两级列表,于是我就用ExpandableListView嵌套ExpandableListView,但非常麻烦,而且关键的是具体分几级是不确定的,也就是可能一级,可能多级,这要是五六级嵌套ListView,于是我就去学习鸿洋大神之前写的一篇关于实现Android多级树形列表的文章,实现很巧妙,使用一个ListView就可以实现多级列表效果,我做了部分修改,功能

  • Java构建树形菜单的实例代码(支持多级菜单)

    效果图:支持多级菜单. 菜单实体类: public class Menu { // 菜单id private String id; // 菜单名称 private String name; // 父菜单id private String parentId; // 菜单url private String url; // 菜单图标 private String icon; // 菜单顺序 private int order; // 子菜单 private List<Menu> children;

  • Android自定义指示器时间轴效果实例代码详解

    指示器时间轴在外卖.购物类的APP里会经常用到,效果大概就像下面这样,看了网上很多文章,大都是自己绘制,太麻烦,其实通过ListView就可以实现. 在Activity关联的布局文件activity_main.xml中放置一个ListView,代码如下.由于这个列表只是用于展示信息,并不需要用户去点击,所以将其clickable属性置为false:为了消除ListView点击产生的波纹效果,我们设置其listSelector属性的值为透明:我们不需要列表项之间的分割线,所以设置其divider属

  • jQuery 利用ztree实现树形表格的实例代码

    最近公司的项目中要做一个树形表格,因为之前一直在用ztree实现基本的树形结构,理所当然的首先想到利用ztree来做. 网上找了一下别人做的树形表格,有使用ztree的,也有使用treeTable的,但效果都不太好,于是参考使用ztree的做法自己做了一个,贴出来供大家参考,请看注释说明,效果如下所示. <!DOCTYPE HTML> <html> <head> <link href="https://cdn.bootcss.com/zTree.v3/3

  • Android 百度地图POI搜索功能实例代码

    在没介绍正文之前先给大家说下poi是什么意思. 由于工作的关系,经常在文件中会看到POI这三个字母的缩写,但是一直对POI的概念和含义没有很详细的去研究其背后代表的意思.今天下班之前,又看到了POI这三个字母,决定认认真真的搜索一些POI具体的含义. POI是英文的缩写,原来的单词是point of interest, 直译成中文就是兴趣点的意思.兴趣点这个词最早来自于导航地图厂商.地图厂商为了提供尽可能多的位置信息,花费了很大的精力去寻找诸如加油站,餐馆,酒店,景点等目的地,这些目的地其实都可

  • Android自定义水波纹动画Layout实例代码

    话不多说,我们先来看看效果: Hi前辈搜索预览 这一张是<Hi前辈>的搜索预览图,你可以在这里下载这个APP查看更多效果: http://www.wandoujia.com/apps/com.superlity.hiqianbei LSearchView 这是一个MD风格的搜索框,集成了ripple动画以及search时的loading,使用很简单,如果你也需要这样的搜索控件不妨来试试:https://github.com/onlynight/LSearchView RippleEverywh

  • js实现无限级树形导航列表效果代码

    本文实例讲述了js实现无限级树形导航列表效果代码.分享给大家供大家参考.具体如下: 这是一款js实现无限级树形下拉导航菜单,简洁实用,用到一个已封装好的JS类,有用的大家借鉴一下. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/js-unlimit-tree-style-nav-list-codes/ 具体代码如下: <meta http-equiv="Content-Type" content="text/html;

  • Android自定义view实现太极效果实例代码

    Android自定义view实现太极效果实例代码 之前一直想要个加载的loading.却不知道用什么好,然后就想到了太极图标,最后效果是有了,不过感觉用来做loading简直丑到爆!!! 实现效果很简单,我们不要用什么贝塞尔曲线啥的,因为太极无非就是圆圆圆,只要画圆就ok了.来上代码: 因为有黑有白,所以定义2个画笔分别为黑和白. private void inital() { whitePaint = new Paint(); whitePaint.setAntiAlias(true); wh

  • 关于Android高德地图的简单开发实例代码(DEMO)

    废话不多说了,直接给大家上干货了. 以下为初次接触时 ,练手的DEMO import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatab

随机推荐