這是一個功能多樣的樹形清單,樹形清單最基本的功能是能夠進行節點的收攏和展開,響應每一級節點的點選事件也是必須的,本篇說的是在我之前實作的樹形清單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);
}
源碼