这是一个功能多样的树形列表,树形列表最基本的功能是能够进行节点的收拢和展开,响应每一级节点的点击事件也是必须的,本篇说的是在我之前实现的树形列表1.0基础上修改完善之后的升级版树形列表。
树形列表1.0具有如下功能:
- 展开收拢;
- 响应点击和选中事件;
- 自定义每个节点的布局和保持的数据结构;
升级版功能:
- 展开收拢时不会改变子节点列表的状态,之前只记录了选中状态;
- 升级版可以根据自己需要来设置选中子节点时是否也需要选中父节点;
- 新增了全部展开和全部收拢的功能,因为是基于DiffUtil实现的,所以在展开全部和收拢全部的时候是局部刷新的。
基本功能在上篇中有详细讲解,本篇主要记录新功能,先上效果图。
收拢时不改变子节点列表状态
上个版本在节点收拢时调用移除节点操作方法时会把展开的子节点关闭
新版本中做了修改,只有在关闭全部节点时才会去收拢子节点,否则只是在列表中移除,但不改变展开收拢状态。
/**
* 收拢时移除节点
*
* @param treeNode
* @param toggle 需要改变当前结点展开收拢
* @param isCloseAll 是否收拢全部
* @return
*/
private int removeNodes(TreeNode treeNode, boolean toggle, boolean isCloseAll) {
int count = 0;
if (!treeNode.isLeaf()) {
List<TreeNode> list = treeNode.getChildNodes();
count += list.size();
expandedList.removeAll(list);
for (TreeNode item : list) {
if (item.isExpanded() && isCloseAll) {
//已展开并且是收拢全部时,关闭
item.toggle();
}
if (item.isExpanded() || isCloseAll) {
//已展开或者是收拢全部的时候,移除子节点
count += removeNodes(item, false, isCloseAll);
}
}
}
if (toggle) {
treeNode.toggle();
}
return count;
}
全部展开和全部收拢
添加这俩个功能的时候我一开始是把所有节点的状态都改变过来之后刷新列表所有项,这种方法相对是比较low的。RecyclerView可以实现列表的局部刷新,而这局部刷新的实现就是使用了DiffUtil方法,比较两个列表的差异,局部添加删除和刷新列表数据。
依然是遍历根列表,将所有子节点列表的展开收拢状态全部改变后,使用DiffUtil实现列表的局部刷新这样的效果才更好。对新旧列表做了遍历比较操作,如果有不同就记录并返回。
/**
* 更新列表
* @param oldList
*/
private void notifyDiff(final List<TreeNode> oldList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return expandedList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
TreeNode oldTreeNode = oldList.get(oldItemPosition);
TreeNode newTreeNode = expandedList.get(newItemPosition);
return oldTreeNode.getValue() != null && oldTreeNode.getValue().equals(newTreeNode.getValue());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
TreeNode oldTreeNode = oldList.get(oldItemPosition);
TreeNode newTreeNode = expandedList.get(newItemPosition);
return oldTreeNode.getValue() != null && oldTreeNode.getValue().equals(newTreeNode.getValue()) &&
oldTreeNode.isExpanded() == newTreeNode.isExpanded() &&
oldTreeNode.isChecked() == newTreeNode.isChecked();
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
TreeNode oldTreeNode = oldList.get(oldItemPosition);
TreeNode newTreeNode = expandedList.get(newItemPosition);
Bundle bundle = new Bundle();
if (oldTreeNode.isExpanded() != newTreeNode.isExpanded()) {
//展开状态不同
bundle.putBoolean(KEY_EXPAND, newTreeNode.isExpanded());
}
if (oldTreeNode.isChecked() != newTreeNode.isChecked()) {
//选中状态不同
bundle.putBoolean(KEY_CHECK, newTreeNode.isChecked());
}
if (bundle.size() == 0) {
bundle = null;
}
return bundle;
}
});
diffResult.dispatchUpdatesTo(this);
}
三个参数的onBindViewHolder中的第三个参数就保持了之前记录下来的不同之处。
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
if (payloads != null && !payloads.isEmpty()) {
Bundle bundle = (Bundle) payloads.get(0);
TreeNode currentNode = expandedList.get(position);
for (String key : bundle.keySet()) {
if (KEY_EXPAND.equals(key) && currentNode.getValue().getToggleId() != 0) {
toggle(((TreeViewBinder.ViewHolder) holder).findViewById(currentNode.getValue().getToggleId()), bundle.getBoolean(key), currentNode);
}
if (KEY_CHECK.equals(key) && currentNode.getValue().getCheckedId() != 0) {
checked(((TreeViewBinder.ViewHolder) holder).findViewById(currentNode.getValue().getCheckedId()), bundle.getBoolean(key), currentNode);
}
}
}
super.onBindViewHolder(holder, position, payloads);
}
源码