树形列表的基本功能是可以展开收拢,该列表还可以响应选中、单项点击事件。使用者可以自定义需要的各级节点的布局样式,在适配器的展开、选中和单项点击回调中实现需要的效果。
效果展示
使用说明
在项目build.gradle中添加
maven { url "https://jitpack.io" }
添加引用(可在 github获取最新版本)
compile 'com.github.q1113225201:TreeView:1.0.2'
实现节点对象和节点对象绑定类,
我这边实现了跟枝页三种不同节点,使用时可以根据需求去实现自己需要的节点对象和布局
初始化树列表,真正显示的列表只需要显示跟节点列表,枝节点添加到根节点下,叶节点添加到枝节点下,也可以直接添加子节点列表
添加到RecyclerView中
源码分析
节点布局接口,因为有了这个类才能实现自定义每个节点的布局,自己实现展开、选中、单项点击的效果
public interface LayoutItem {
/**
* 返回布局id
* @return
*/
int getLayoutId();
/**
* 返回展开收拢事件触发id
* @return
*/
int getToggleId();
/**
* 返回选中事件触发id
* @return
*/
int getCheckedId();
/**
* 返回单项点击事件触发id
* @return
*/
int getClickId();
}
自定义的节点对象继承TreeNode,自定义的数据都放在value属性中,该对象保存有当前节点父节点、子节点列表、展开状态、选中状态和所在树层级
public class TreeNode<T extends LayoutItem> {
/**
* 当前节点值
*/
private T value;
/**
* 父节点
*/
private TreeNode parentNode;
/**
* 孩子节点列表
*/
private List<TreeNode> childNodes = new ArrayList<>();
/**
* 是否已展开
*/
private boolean expanded;
/**
* 是否被选中
*/
private boolean checked;
/**
* 层级
*/
private int level = 0;
public TreeNode(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public TreeNode getParentNode() {
return parentNode;
}
public void setParentNode(TreeNode parentNode) {
this.parentNode = parentNode;
}
public List<TreeNode> getChildNodes() {
return childNodes;
}
public void setChildNodes(List<TreeNode> childNodes) {
if (childNodes == null) {
childNodes = new ArrayList<>();
}
for (int i = 0; i < childNodes.size(); i++) {
childNodes.get(i).setParentNode(this);
}
initLevel(childNodes);
this.childNodes = childNodes;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public boolean toggle() {
expanded = !expanded;
return expanded;
}
public boolean open() {
expanded = true;
return expanded;
}
public boolean close() {
expanded = false;
return expanded;
}
public boolean isRoot() {
return parentNode == null;
}
public boolean isLeaf() {
return childNodes == null || childNodes.isEmpty();
}
public void addChild(TreeNode treeNode) {
if (childNodes == null) {
childNodes = new ArrayList<>();
}
childNodes.add(treeNode);
initLevel(childNodes);
}
private void initLevel(List<TreeNode> childNodes) {
for (TreeNode item : childNodes) {
item.setLevel(item.getParentNode().getLevel() + 1);
if (!item.getChildNodes().isEmpty()) {
initLevel(item.childNodes);
}
}
}
@Override
public boolean equals(Object obj) {
TreeNode treeNode = (TreeNode) obj;
return value.equals(treeNode.getValue())
&& ((parentNode != null && parentNode.equals(treeNode.getParentNode())) || (parentNode == null && treeNode.getParentNode() == null))
&& childNodes.equals(treeNode.getChildNodes())
&& expanded == treeNode.isExpanded()
&& checked == treeNode.isChecked();
}
}
树形节点绑定抽象,该类有创建视图和绑定视图抽象方法,使用过RecyclerView的应该都非常熟悉
public abstract class TreeViewBinder<VH extends RecyclerView.ViewHolder> implements LayoutItem {
/**
* 创建视图
* @param itemView
* @return
*/
public abstract VH creatViewHolder(View itemView);
/**
* 绑定视图
* @param holder
* @param position
* @param treeNode
*/
public abstract void bindViewHolder(VH holder, int position, TreeNode treeNode);
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
public <T extends View> T findViewById(int id) {
return itemView.findViewById(id);
}
}
}
适配器中实现了节点展开和收拢时列表的变化,选中效果的改变也是通过RecyclerView的局部更新实现的
/**
* 节点展开收拢
*
* @param currentNode
*/
public void toggle(TreeNode currentNode) {
boolean isExpanded = currentNode.isExpanded();
int startPosition = expandedList.indexOf(currentNode) + 1;
if (isExpanded) {
notifyItemRangeRemoved(startPosition, removeNodes(currentNode, true));
} else {
notifyItemRangeInserted(startPosition, insertNodes(currentNode, startPosition));
}
}
/**
* 收拢时移除节点
*
* @param treeNode
* @param toggle
* @return
*/
private int removeNodes(TreeNode treeNode, boolean toggle) {
int count = 0;
if (!treeNode.isLeaf()) {
List<TreeNode> list = treeNode.getChildNodes();
count += list.size();
expandedList.removeAll(list);
for (TreeNode item : list) {
if (item.isExpanded()) {
item.toggle();
}
count += removeNodes(item, false);
}
}
if (toggle) {
treeNode.toggle();
}
return count;
}
/**
* 展开时插入节点
*
* @param treeNode
* @param startPosition
* @return
*/
private int insertNodes(TreeNode treeNode, int startPosition) {
List<TreeNode> list = treeNode.getChildNodes();
int count = 0;
for (TreeNode item : list) {
expandedList.add(startPosition + count, item);
count++;
if (item.isExpanded()) {
count += insertNodes(item, startPosition + count);
}
}
if (!treeNode.isExpanded()) {
treeNode.setExpanded(!treeNode.isExpanded());
}
return count;
}
源码