在一次开发中,碰到了多级树形列表的问题,不是我们平时使用的ExpandListView这种只有子父类两级的列表,而是层级更深。
推荐几个可以参考的博客:
1、鸿神的ListView实现多级树形列表
2、一个兄弟在鸿神的基础上修改的
我就是使用的这个兄弟的,在他的基础上修改为我所需要的效果。
3、也可以看看这个兄弟的,他就是参考上面1、2做出来的他想要的效果
他们都是实现的多选,这次开发需求是实现单选,而我使用的RecyclerView,就没去考虑ListView的用法,大家有需要的可以自己去尝试修改。
首先就是我们去封装我们的节点类,一般我们至少需要当前节点的id,当前节点的父节点pid,还有就是节点的名字。
节点类 我是直接使用的我也没有删除没用的一些属性,只是添加了我需要的属性,你们可以任意添加你需要的数据,个人感觉泛型好像不是很有用。
public class Node<T, B> implements Serializable {
/**
* 传入的实体对象
*/
public B bean;
/**
* 设置开启的图片
*/
public int iconExpand = -1;
/**
* 设置关闭的图片
*/
public int iconNoExpand = -1;
private T id;
/**
* 根节点pId为0
*/
private T pId;
private String name;
/**
* 当前的级别
*/
private int level;
/**
* 是否展开
*/
private boolean isExpand = false;
private int icon = -1;
/**
* 下一级的子Node
*/
private List<Node> children = new ArrayList<>();
/**
* 父Node
*/
private Node parent;
/**
* 是否被checked选中
*/
private boolean isChecked;
/**
* 是否为新添加的
*/
public boolean isNewAdd = true;
/**
* 单选标记
*/
public boolean isSingle;
/**
* 支部code
*/
public String braCode;
public Node(T id, T pId, String name) {
super();
this.id = id;
this.pId = pId;
this.name = name;
}
public Node(T id, T pId, String name, B bean) {
super();
this.id = id;
this.pId = pId;
this.name = name;
this.bean = bean;
}
public String getBraCode() {
return braCode;
}
public void setBraCode(String braCode) {
this.braCode = braCode;
}
public boolean isSingle() {
return isSingle;
}
public void setSingle(boolean single) {
isSingle = single;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
public Node() {
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public T getpId() {
return pId;
}
public void setpId(T pId) {
this.pId = pId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setLevel(int level) {
this.level = level;
}
public boolean isExpand() {
return isExpand;
}
public List<Node> getChildren() {
return children;
}
public void setChildren(List<Node> children) {
this.children = children;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
/**
* 是否为跟节点
*
* @return
*/
public boolean isRoot() {
return parent == null;
}
/**
* 判断父节点是否展开
*
* @return
*/
public boolean isParentExpand() {
if (parent == null){
return false;
}
return parent.isExpand();
}
/**
* 是否是叶子界点
*
* @return
*/
public boolean isLeaf() {
return children.size() == 0;
}
/**
* 获取level
*/
public int getLevel() {
return parent == null ? 0 : parent.getLevel() + 1;
}
/**
* 设置展开
*
* @param isExpand
*/
public void setExpand(boolean isExpand) {
this.isExpand = isExpand;
if (!isExpand) {
for (Node node : children) {
node.setExpand(false);
}
}
}
}
item点击监听接口
public interface OnTreeNodeClickListener {
/**
* 点击事件方法
* @param node 节点
* @param position 条目位置
*/
void onClick(Node node, int position);
}
ThreeHelper类
唯一需要修改的就是将int使用==比较修改为使用equals()来进行值的比较,否则你传入int类型的值的时候会发现根本就没有给你分级出来。
public class TreeHelper {
/**
* 传入node 返回排序后的Node
*
* @param datas 传入数据
* @param defaultExpandLevel 默认展开等级
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static List<Node> getSortedNodes(List<Node> datas, int defaultExpandLevel) {
List<Node> result = new ArrayList<Node>();
// 设置Node间父子关系
List<Node> nodes = convetData2Node(datas);
// 拿到根节点
List<Node> rootNodes = getRootNodes(nodes);
// 排序以及设置Node间关系
for (Node node : rootNodes) {
addNode(result, node, defaultExpandLevel, 1);
}
return result;
}
/**
* 过滤出所有可见的Node
*
* @param nodes
* @return
*/
public static List<Node> filterVisibleNode(List<Node> nodes) {
List<Node> result = new ArrayList<Node>();
for (Node node : nodes) {
// 如果为跟节点,或者上层目录为展开状态
if (node.isRoot() || node.isParentExpand()) {
setNodeIcon(node);
result.add(node);
}
}
return result;
}
/**
* 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系
*/
private static List<Node> convetData2Node(List<Node> nodes) {
for (int i = 0; i < nodes.size(); i++) {
Node n = nodes.get(i);
for (int j = i + 1; j < nodes.size(); j++) {
Node m = nodes.get(j);
//判断id是String,Integer
if (m.getpId() instanceof String) {
if (m.getpId().equals(n.getId())) {
n.getChildren().add(m);
m.setParent(n);
} else if (m.getId().equals(n.getpId())) {
m.getChildren().add(n);
n.setParent(m);
}
} else if(m.getpId() instanceof Integer) {
//这里就是修改的地方,因为这是Integer类,节点的地址是不一样的,所以不能直接使用==比较,要用equals()进行值的比较
if (m.getpId().equals(n.getId())) {
n.getChildren().add(m);
m.setParent(n);
} else if (m.getId().equals(n.getpId())) {
m.getChildren().add(n);
n.setParent(m);
}
}
}
}
return nodes;
}
/**
* 获取所有根节点
*
* @param nodes
* @return
*/
private static List<Node> getRootNodes(List<Node> nodes) {
List<Node> root = new ArrayList<Node>();
for (Node node : nodes) {
if (node.isRoot()) {
root.add(node);
}
}
return root;
}
/**
* 把一个节点上的所有的内容都挂上去
*/
private static <T, B> void addNode(List<Node> nodes, Node<T, B> node,
int defaultExpandLeval, int currentLevel) {
nodes.add(node);
//判断添加新节点的时候,保持以前的状态不变
if (node.isNewAdd && defaultExpandLeval >= currentLevel) {
node.setExpand(true);
}
if (node.isLeaf()) {
//判断是子节点
return;
}
for (int i = 0; i < node.getChildren().size(); i++) {
addNode(nodes, node.getChildren().get(i), defaultExpandLeval,
currentLevel + 1);
}
}
/**
* 设置节点的图标
*
* @param node
*/
private static void setNodeIcon(Node node) {
if (node.getChildren().size() > 0 && node.isExpand()) {
node.setIcon(node.iconExpand);
} else if (node.getChildren().size() > 0 && !node.isExpand()) {
node.setIcon(node.iconNoExpand);
} else {
node.setIcon(-1);
}
}
}
然后就是Adapter的修改==》TreeRecyclerAdapter
因为我需要的是单选,所以我在adapter中做了修改,如果你使用多选,就可以直接使用上面2那个博主封装好的。
public abstract class TreeRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
protected Context mContext;
/**
* 存储所有可见的Node
*/
protected List<Node> mNodes = new ArrayList<>();
protected LayoutInflater mInflater;
/**
* 存储所有的Node
*/
protected List<Node> mAllNodes = new ArrayList<>();
/**
* 点击的回调接口
*/
private OnTreeNodeClickListener onTreeNodeClickListener;
/**
* 默认不展开
*/
private int defaultExpandLevel = 0;
/**
* 展开与关闭的图片
*/
private int iconExpand = -1, iconNoExpand = -1;
/**
* 当前被点击的位置
*/
private int layoutPosition = -1;
public void setOnTreeNodeClickListener(OnTreeNodeClickListener onTreeNodeClickListener) {
this.onTreeNodeClickListener = onTreeNodeClickListener;
}
public TreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas,
int defaultExpandLevel, int iconExpand, int iconNoExpand) {
this.iconExpand = iconExpand;
this.iconNoExpand = iconNoExpand;
for (Node node : datas) {
node.getChildren().clear();
node.iconExpand = iconExpand;
node.iconNoExpand = iconNoExpand;
}
this.defaultExpandLevel = defaultExpandLevel;
mContext = context;
/**
* 对所有的Node进行排序
*/
mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);
/**
* 过滤出可见的Node
*/
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
mInflater = LayoutInflater.from(context);
}
/**
* @param mTree
* @param context
* @param datas
* @param defaultExpandLevel 默认展开几级树
*/
public TreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas,
int defaultExpandLevel) {
this(mTree, context, datas, defaultExpandLevel, -1, -1);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
Node node = mNodes.get(position);
// convertView = getConvertView(node, position, convertView, parent);
// 设置内边距
int padding = SizeUtils.dp2px(mContext,15f);
holder.itemView.setPadding(SizeUtils.dp2px(mContext,(node.getLevel()+1) * 15),
padding,padding,padding);
//然后设置单选,修改如下
/**
* 设置节点点击时,可以展开以及关闭,将事件继续往外公布
*/
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取当前点击的位置
layoutPosition = holder.getLayoutPosition();
expandOrCollapse(position);
if (onTreeNodeClickListener != null) {
onTreeNodeClickListener.onClick(mNodes.get(position),
position);
}
}
});
if(position == layoutPosition){
node.setSingle(true);
}else {
node.setSingle(false);
}
onBindViewHolder(node, holder, position);
}
@Override
public int getItemCount() {
return mNodes.size();
}
/**
* 清除掉之前数据并刷新 重新添加
*
* @param mlists
* @param defaultExpandLevel 默认展开几级列表
*/
public void addDataAll(List<Node> mlists, int defaultExpandLevel) {
mAllNodes.clear();
addData(-1, mlists, defaultExpandLevel);
}
/**
* 在指定位置添加数据并刷新 可指定刷新后显示层级
*
* @param index
* @param mlists
* @param defaultExpandLevel 默认展开几级列表
*/
public void addData(int index, List<Node> mlists, int defaultExpandLevel) {
this.defaultExpandLevel = defaultExpandLevel;
notifyData(index, mlists);
}
/**
* 在指定位置添加数据并刷新
*
* @param index
* @param mlists
*/
public void addData(int index, List<Node> mlists) {
notifyData(index, mlists);
}
/**
* 添加数据并刷新
*
* @param mlists
*/
public void addData(List<Node> mlists) {
addData(mlists, defaultExpandLevel);
}
/**
* 添加数据并刷新 可指定刷新后显示层级
*
* @param mlists
* @param defaultExpandLevel
*/
public void addData(List<Node> mlists, int defaultExpandLevel) {
this.defaultExpandLevel = defaultExpandLevel;
notifyData(-1, mlists);
}
/**
* 添加数据并刷新
*
* @param node
*/
public void addData(Node node) {
addData(node, defaultExpandLevel);
}
/**
* 添加数据并刷新 可指定刷新后显示层级
*
* @param node
* @param defaultExpandLevel
*/
public void addData(Node node, int defaultExpandLevel) {
List<Node> nodes = new ArrayList<>();
nodes.add(node);
this.defaultExpandLevel = defaultExpandLevel;
notifyData(-1, nodes);
}
/**
* 刷新数据
*
* @param index
* @param mListNodes
*/
private void notifyData(int index, List<Node> mListNodes) {
for (int i = 0; i < mListNodes.size(); i++) {
Node node = mListNodes.get(i);
node.getChildren().clear();
node.iconExpand = iconExpand;
node.iconNoExpand = iconNoExpand;
}
for (int i = 0; i < mAllNodes.size(); i++) {
Node node = mAllNodes.get(i);
node.getChildren().clear();
node.isNewAdd = false;
}
if (index != -1) {
mAllNodes.addAll(index, mListNodes);
} else {
mAllNodes.addAll(mListNodes);
}
/**
* 对所有的Node进行排序
*/
mAllNodes = TreeHelper.getSortedNodes(mAllNodes, defaultExpandLevel);
/**
* 过滤出可见的Node
*/
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
//刷新数据
notifyDataSetChanged();
}
/**
* 获取排序后所有节点
*
* @return
*/
public List<Node> getAllNodes() {
if (mAllNodes == null){
mAllNodes = new ArrayList<Node>();
}
return mAllNodes;
}
/**
* 相应ListView的点击事件 展开或关闭某节点
*
* @param position
*/
public void expandOrCollapse(int position) {
Node n = mNodes.get(position);
if (n != null) {
// 排除传入参数错误异常
if (!n.isLeaf()) {
n.setExpand(!n.isExpand());
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
notifyDataSetChanged();// 刷新视图
}
}
}
/**
* 设置多选
*
* @param node
* @param checked
*/
protected void setChecked(final Node node, boolean checked) {
node.setChecked(checked);
setChildChecked(node, checked);
if (node.getParent() != null){
setNodeParentChecked(node.getParent(), checked);
}
notifyDataSetChanged();
}
/**
* 设置是否选中
*
* @param node
* @param checked
*/
public <T, B> void setChildChecked(Node<T, B> node, boolean checked) {
if (!node.isLeaf()) {
node.setChecked(checked);
for (Node childrenNode : node.getChildren()) {
setChildChecked(childrenNode, checked);
}
} else {
node.setChecked(checked);
}
}
private void setNodeParentChecked(Node node, boolean checked) {
if (checked) {
node.setChecked(checked);
if (node.getParent() != null){
setNodeParentChecked(node.getParent(), checked);
}
} else {
List<Node> childrens = node.getChildren();
boolean isChecked = false;
for (Node children : childrens) {
if (children.isChecked()) {
isChecked = true;
}
}
//如果所有自节点都没有被选中 父节点也不选中
if (!isChecked) {
node.setChecked(checked);
}
if (node.getParent() != null){
setNodeParentChecked(node.getParent(), checked);
}
}
}
public abstract void onBindViewHolder(Node node, RecyclerView.ViewHolder holder, final int position);
//===================在这里添加修改选中位置的方法=====================
/**
* 设置选中位置
* @param selectedPosition
*/
public void setSelectedPosition(int selectedPosition) {
this.layoutPosition = selectedPosition;
}
}
最后你在使用的时候,实现Adapter的时候去继承TreeRecyclerAdapter这个类,实现如下
public class MulBranchChoiceAdapter extends TreeRecyclerAdapter {
private Context context;
private List<Node> datas;
private LayoutInflater inflater;
public MulBranchChoiceAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {
super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);
this.context = context;
this.datas = datas;
this.inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public MulBranchChoiceAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel) {
super(mTree, context, datas, defaultExpandLevel);
this.context = context;
this.datas = datas;
this.inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public void onBindViewHolder(Node node, RecyclerView.ViewHolder holder, int position) {
MulBranchChoiceViewHolder viewHolder = (MulBranchChoiceViewHolder) holder;
//这是我在Node节点类中添加的一个保存单选状态的属性,所以直接这里就使用到了
if(node.isSingle){
viewHolder.ivChoice.setImageResource(R.drawable.select_icon);
}else{
viewHolder.ivChoice.setImageResource(R.drawable.circle_icon);
}
if (node.getIcon() == -1) {
viewHolder.ivTree.setVisibility(View.GONE);
} else {
viewHolder.ivTree.setVisibility(View.VISIBLE);
viewHolder.ivTree.setImageResource(node.getIcon());
}
viewHolder.tvBranch.setText(node.getName());
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MulBranchChoiceViewHolder(inflater.inflate(R.layout.multiple_tree_list_item,null));
}
class MulBranchChoiceViewHolder extends RecyclerView.ViewHolder{
ImageView ivChoice;
ImageView ivTree;
TextView tvBranch;
public MulBranchChoiceViewHolder(View itemView) {
super(itemView);
ivChoice = (ImageView) itemView.findViewById(R.id.iv_choice);
ivTree = (ImageView) itemView.findViewById(R.id.iv_tree);
tvBranch = (TextView) itemView.findViewById(R.id.tv_branch);
}
}
}
然后在Activity中使用,我们记住我们要讲服务器返回给我们的数据,封装成进Node实体类。
需要注意:
1、我们这实现的是单选,需要更新选中位置
/**
* 更新选择窗
*
* @param selectedPosition 选中位置
*/
public void updatePosition(int selectedPosition) {
if (adapter != null) {
adapter.setSelectedPosition(selectedPosition);
adapter.notifyDataSetChanged();
}
}
2、需要注意我们的点击事件,我们此时去取出的数据是我们回调方法中Node的数据,不是使用list.get(position)去取出数据,因为我们在ThreeRecyclerAdapter类中对传入的list数据进行了排序,当前位置的数据已经发生了改变,再次使用插入的list去取数据就会出问题。