列表与树的处理

列表与树的处理

最近做一个功能需要将数据库的数据转换成树结构,可以转换成树的数据往往数据库里的表字段含有parent_id这种代表关系,突发奇想,做一个泛型的树工具类,以后遇到这种结构都可以直接转换成树使用。

对象必须具有转换成树的能力

适用于具有父子关系的属性的列表与树的转换,这里封装成了接口TreeAble,提供了找到父节点和自身的索引,其中泛型K是索引的类型。

public interface TreeAble<K> {

    K getKey();

    K getParentKey();

}

树节点对象

每个树的节点应该是有索引,子节点和树节点的值等属性,完善一点可以包含表示父节点的属性,这里不再冗余,以下结构已经可以表示树的结构了。这里使用了lombok的注解省去了构造器和get,set方法,其中泛型V即是树节点的值。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode<K, V> {
    private K key;

    private V value;

    private List<TreeNode<K, V>> children;

}

将列表转化成树

准备工作做好后,就可以写工具类了,首先是将数据库里查找出来的列表对象转化成树结构

public static <K, V extends TreeAble<K>> List<TreeNode<K, V>> createTreeByList(List<V> list) {
    //用于存放树节点,方便找到
    HashMap<K, TreeNode<K, V>> map = new HashMap<>();
    //用于存放未找到父节点的对象,此类对象进入下次循环,直到所有对象找到父节点
    Set<V> noParent = new HashSet<>();
    //虚拟一个最顶层的节点
    TreeNode<K, V> top = new TreeNode<>();
    //容错处理,循环20次未找到父节点,证明list可能存在环结构,父节点不存在的对象,否则可能出现死循环
    int maxNum = 20;
    while (list.size() > 0) {
        for (V v : list) {
            //新建树节点,并存放到map中,方便子节点
            TreeNode<K, V> treeNode = new TreeNode<>(v.getKey(), v, null);
            map.put(v.getKey(), treeNode);
            //父节点为null或者父节点是自己,说明是顶层节点,他的节点就是虚拟顶层节点
            TreeNode<K, V> parent = v.getParentKey() == null || v.getParentKey().equals(v.getKey()) ? top : map.get(v.getParentKey());
            //父节点如果没有找到,存放到noParent,下次便利是继续
            if (parent == null) {
                noParent.add(v);
                continue;
            }
            //父节点如果找到了,添加到父节点
            if (parent.getChildren() == null) {
                parent.setChildren(new ArrayList<>());
            }
            parent.getChildren().add(treeNode);
        }
        //重新设置list为没有找到父节点的对象,进入下次循环
        list = new ArrayList<>(noParent);
        noParent.clear();
        //死循环容错处理
        maxNum--;
        if (maxNum == 0) {
            throw new IllegalArgumentException("参数异常,不能组成树结构,请检查是否为(环,父节点不存在,数据过于庞大)");
        }
    }
    //最后返回虚拟顶层节点的子节点既是列表转换后的树结构
    return top.getChildren();
}

可以扩展成list转化成单个树

public static <K, V extends TreeAble<K>> TreeNode<K, V> createSingleTreeByList(List<V> list) {
    List<TreeNode<K, V>> treeNodes = createTreeByList(list);
    if (treeNodes == null || treeNodes.size() != 1) {
        throw new IllegalArgumentException("参数异常,不是简单树结构");
    }
    return treeNodes.get(0);
}

查找一个树下面所有的节点

仅仅转换成树可能还不够,在实际应用中还有很多是需要进一步处理的,比如需要查看一个树下面所有的节点。

public static <K, V extends TreeAble<K>> List<V> getAllChildrenByKey(List<TreeNode<K, V>> tree, K key) {
    //采用层级查找的方法,topTree缓存下一次需要查找的层级的所有树节点,知道层级到最后一层
    List<TreeNode<K, V>> topTree = new ArrayList<>();
    while (tree.size() > 0) {
        for (TreeNode<K, V> node : tree) {
            //当遍历的树节点的key等于目标key时,找到这个节点下的的所有对象,并返回
            if (key.equals(node.getKey())) {
                List<V> allNode = getAllNode(node.getChildren());
                allNode.add(node.getValue());
                return allNode;
            } else {
                //如果没找到目标key对应的节点,进入下一个层级搜索
                if (node.getChildren() != null) {
                    topTree.addAll(node.getChildren());
                }
            }
        }
        tree = new ArrayList<>(topTree);
        topTree.clear();
    }
    return new ArrayList<>();
}
//找到这些节点下的的所有对象
public static <K, V extends TreeAble<K>> List<V> getAllNode(List<TreeNode<K, V>> tree) {
    List<V> result = new ArrayList<>();
    if (tree != null) {
        for (TreeNode<K, V> treeNode : tree) {
            //如果有子节点,递归查询这些节点下的的所有对象,最后加上自己本身
            if (treeNode.getChildren() != null) {
                result.addAll(getAllNode(treeNode.getChildren()));
            }
            result.add(treeNode.getValue());
        }
    }
    return result;
}

可以扩展成可树化的列表查找下方所有的节点

public static <K, V extends TreeAble<K>> List<V> getAllChildrenByKeyFromList(List<V> tree, K key) {
    List<TreeNode<K, V>> treeByList = createTreeByList(tree);
    return getAllChildrenByKey(treeByList, key);
}

测试类

可树化的类:

@Data
@AllArgsConstructor
public class TreeDemo implements TreeAble<String> {
    private String id;
    private String parentId;

    @Override
    public String getKey() {
        return id;
    }

    @Override
    public String getParentKey() {
        return parentId;
    }
}

Main函数:

public static void main(String[] args) {
    List<TreeDemo> treeDemos = Arrays.asList(
        new TreeDemo("1", "1"),
        new TreeDemo("2", "1"),
        new TreeDemo("3", "1"),
        new TreeDemo("4", "1"),
        new TreeDemo("5", "3"),
        new TreeDemo("6", "2"),
        new TreeDemo("7", "5"),
        new TreeDemo("8", "7"),
        new TreeDemo("9", "9"),
        new TreeDemo("10", "9"),
        new TreeDemo("11", "9"),
        new TreeDemo("12", "2"),
        new TreeDemo("13", "4"),
        new TreeDemo("14", "6"),
        new TreeDemo("15", "6"),
        new TreeDemo("16", "1")
    );
    List<TreeNode<String, TreeDemo>> treeNodes = TreeUtil.createTreeByList(treeDemos);
    //TreeNode<String, TreeTest> treeNodes = TreeUtil.creteSingleTreeByList(treeTests);
    System.out.println(treeNodes);
    //List<TreeDemo> allChildrenByKey = TreeUtil.getAllChildrenByKey(treeNodes, "9");
    List<TreeDemo> allChildrenByKey = TreeUtil.getAllChildrenByKeyFromList(treeDemos, "1");
    System.out.println(allChildrenByKey);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速实现Android多级列表,这个库是在鸿洋多级列表demo中修改而来。解决的问题:支持ID为int类型和String类型。支持多级复选框选中,使用只需一行代码。支持动态更新数据并保持原有展开/关闭状态。支持ListView、RecyclerView。USE我们关联列表需要有三个必须元素,当前id、父级id即pid,显示的内容。id和pid可以为int或者String以及其他类型。要显示的内容需要包装一下://id pid name  FileNode为实际用的实体Bean对象 mlist.add(new Node("223","0","我也是添加的root节点",new FileNode()));对于ListView,需要继承自TreeListViewAdapter,如:public class SimpleTreeAdapter extends TreeListViewAdapter {     public SimpleTreeAdapter(ListView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeAdapter(ListView mTree, Context context, List datas,                              int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public View getConvertView(final Node node , int position, View convertView, ViewGroup parent)     {        final ViewHolder viewHolder ;         if (convertView == null) {             convertView = mInflater.inflate(R.layout.list_item, parent, false);             viewHolder = new ViewHolder();             viewHolder.cb = (CheckBox) convertView                     .findViewById(R.id.cb_select_tree);             viewHolder.label = (TextView) convertView                     .findViewById(R.id.id_treenode_label);             viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);             convertView.setTag(viewHolder);         } else {             viewHolder = (ViewHolder) convertView.getTag();         }         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());         return convertView;     }     private final class ViewHolder     {         ImageView icon;         CheckBox cb;         TextView label;     } }对于RecyclerView,需继承自TreeRecyclerAdapter,如:public class SimpleTreeRecyclerAdapter extends TreeRecyclerAdapter {     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {         return new MyHoder(View.inflate(mContext, R.layout.list_item,null));     }     @Override     public void onBindViewHolder(final Node node, RecyclerView.ViewHolder holder, int position) {         final MyHoder viewHolder = (MyHoder) holder;         //todo do something         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());     }     class MyHoder extends RecyclerView.ViewHolder{         public CheckBox cb;         public TextView label;         public ImageView icon;         public MyHoder(View itemView) {             super(itemView);             cb = (CheckBox) itemView                     .findViewById(R.id.cb_select_tree);             label = (TextView) itemView                     .findViewById(R.id.id_treenode_label);             icon = (ImageView) itemView.findViewById(R.id.icon);         }     } }初始化: ListView://第一个参数  ListView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeAdapter(mTree, ListViewActivity.this,                         mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);RecyclerView://第一个参数  RecyclerView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeRecyclerAdapter(mTree, RecyclerViewActivity.this,                 mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);添加数据,可以保持原有选中或者展开状态:List mlist = new ArrayList(); mlist.add(new Node("223","0","我也是添加的root节点",new FileNode())); mAdapter.addData(0,mlist);获取选中内容:如果node的isChecked()为true,即为选中状态。StringBuilder sb = new StringBuilder(); //获取排序过的nodes //如果不需要刻意直接用 mDatas既可 final List allNodes = mAdapter.getAllNodes(); for (int i = 0; i < allNodes.size(); i ) {    if (allNodes.get(i).isChecked()){        sb.append(allNodes.get(i).getName() ",");    } } String strNodesName = sb.toString(); if (!TextUtils.isEmpty(strNodesName))    Toast.makeText(this, strNodesName.substring(0, strNodesName.length()-1),Toast.LENGTH_SHORT).show();控制父子之间联动的选中与取消状态,只需调用setChecked方法既可,注意如果在setOnCheckedChangeListener中处理会有问题:因为如果要子节点/父节点选中或者取消需要刷新页面,而刷新页面又会触发viewHolder.cb.setChecked(true/false);的判断从而又会进入setOnCheckedChangeListener,会导致如果父节点选中某些子节点取消不了的情况。//viewHolder.cb 为CheckBox  viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值