Всем привет. Я ~неделю назад захотел написать дерево файлов под Андроид на основе RecyclerView. Собственно я написал все кроме openFolder/closeFolder за день. Остальное время я пытался написать эти функции... И все это время, меня преследовал только 1 баг :(
Он ниже:
Адаптер:
package org.xedox.treeview.widget;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.DiffUtil;
import static androidx.recyclerview.widget.RecyclerView.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.xedox.treeview.utils.DiffCallBack;
import org.xedox.treeview.utils.Node;
import org.xedox.treeview.R;
public class FileTreeAdapter extends Adapter<FileTreeAdapter.VH> {
public List<Node> nodes = new ArrayList<>();
public List<Node> oldnodes = new ArrayList<>();
public Node root;
public Context context;
public int fileItemLayout = R.layout.file_item;
public int indent = 20;
public Map<String, Integer> icons = new HashMap<>();
public OnFileClickListener onFileClickListener;
public OnFileLongClickListener onFileLongClickListener;
public FileTreeAdapter(Context context) {
this.context = context;
icons.put("file", R.drawable.file);
icons.put("folder", R.drawable.folder);
}
class VH extends ViewHolder {
public ImageButton isOpen;
public ImageView icon;
public TextView name;
public View parent;
public VH(View root) {
super(root);
parent = root.findViewById(R.id.parent);
icon = root.findViewById(R.id.icon);
name = root.findViewById(R.id.name);
isOpen = root.findViewById(R.id.isOpen);
}
public void bind(Node node) {
parent.setTranslationX(indent * node.level);
name.setText(node.name);
if (node.isFile) initFile(node);
else initFolder(node);
if (onFileClickListener != null) {
parent.setOnClickListener(
(v) -> {
onFileClickListener.onClick(node, new File(node.fullPath));
});
}
if (onFileLongClickListener != null) {
parent.setOnLongClickListener(
(v) -> {
onFileLongClickListener.onClick(node, new File(node.fullPath));
return true;
});
}
}
public void initFile(Node node) {
if (icons != null) {
boolean found = false;
for (String key : icons.keySet()) {
if (key.equals("folder")) continue;
if (node.name.endsWith(key)) {
icon.setImageResource(icons.get(key));
found = true;
break;
}
}
if (!found) {
icon.setImageResource(R.drawable.file);
}
} else {
icon.setImageResource(R.drawable.folder);
}
}
public void initFolder(Node node) {
if (icons != null) {
icon.setImageResource(icons.get("folder"));
} else {
icon.setImageResource(R.drawable.folder);
}
isOpen.setImageResource(R.drawable.arrow_up);
isOpen.setRotation(node.isOpen ? 180 : 0);
View.OnClickListener ocl =
(v) -> {
node.isOpen = !node.isOpen;
if (node.isOpen) {
openFolder(node);
isOpen.setRotation(180);
} else {
closeFolder(node, true);
isOpen.setRotation(0);
}
};
isOpen.setOnClickListener(ocl);
parent.setOnClickListener(ocl);
}
}
@Override
public VH onCreateViewHolder(ViewGroup vg, int pos) {
View root = LayoutInflater.from(context).inflate(R.layout.file_item, vg, false);
VH vh = new VH(root);
return vh;
}
@Override
public void onBindViewHolder(VH vh, int pos) {
Node n = nodes.get(pos);
vh.bind(n);
}
@Override
public int getItemCount() {
return nodes.size();
}
public void refresh() {
List<Node> newList = new ArrayList<>(nodes);
List<Node> oldList = new ArrayList<>(oldnodes);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldList, newList));
nodes.clear();
nodes.addAll(newList);
diffResult.dispatchUpdatesTo(this);
oldnodes = new ArrayList<>(newList);
}
public void removeNode(Node node) {
nodes.remove(node);
refresh();
}
public void removeNode(int pos) {
nodes.remove(pos);
refresh();
}
public interface OnFileClickListener {
public void onClick(Node node, File file);
}
public interface OnFileLongClickListener {
public void onClick(Node node, File file);
}
public void openFolder(Node node) {
openFolder2(node);
refresh();
}
private void openFolder2(Node node) {
node.isOpen = true;
int index = nodes.indexOf(node);
int insertPosition = (index == -1) ? nodes.size() : index + 1;
for (Node child : node.children) {
if (!nodes.contains(child)) {
nodes.add(insertPosition, child);
insertPosition++;
}
if (!child.isFile) {
child.isOpen = true;
openFolder2(child);
}
}
}
private int start, end;
private List<Integer> indicesToRemove;
public void closeFolder(Node node, boolean isRoot) {
if (isRoot) {
start = nodes.indexOf(node);
end = start;
indicesToRemove = new ArrayList<>();
}
if (node.children.size() > 0) {
for (Node c : node.children) {
end++;
int index = nodes.indexOf(c);
indicesToRemove.add(index);
if (!c.isFile && c.isOpen) {
closeFolder(c, false);
}
}
}
if (isRoot) {
node.isOpen = false;
Collections.sort(indicesToRemove, Collections.reverseOrder());
for (int index : indicesToRemove) {
nodes.remove(index);
}
indicesToRemove.clear();
refresh();
}
}
public void setRoot(Node newRoot) {
this.root = newRoot;
root.isOpen = false;
root.level = 0;
nodes.clear();
nodes.add(newRoot);
}
}
DiffCallBack:
package org.xedox.treeview.utils;
import androidx.recyclerview.widget.DiffUtil;
import java.util.List;
import java.util.Objects;
public class DiffCallBack extends DiffUtil.Callback {
public List<Node> oldL, newL;
public DiffCallBack(List<Node> oldL, List<Node> newL) {
this.oldL = oldL;
this.newL = newL;
}
@Override
public int getOldListSize() {
return oldL.size();
}
@Override
public int getNewListSize() {
return newL.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Node oldN = oldL.get(oldItemPosition);
Node newN = newL.get(newItemPosition);
return oldN.id == newN.id;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Node oldN = oldL.get(oldItemPosition);
Node newN = newL.get(newItemPosition);
return Objects.equals(oldN.name, newN.name)
&& Objects.equals(oldN.path, newN.path)
&& Objects.equals(oldN.fullPath, newN.fullPath)
&& oldN.level == newN.level
&& oldN.isOpen == newN.isOpen
&& oldN.isFile == newN.isFile;
}
}
Node:
package org.xedox.treeview.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class Node {
public String name;
public String path;
public String fullPath;
public List<Node> children;
public boolean isFile;
public boolean isOpen;
public int level = 0;
public int id;
public static int idCount = 0;
public Node() {
name = "";
path = "";
fullPath = "";
children = new ArrayList<>();
isOpen = false;
isFile = true;
id = idCount++;
fullPath = path + "/" + name;
}
@Override
public String toString() {
return String.format(
"Node{name='%s', path='%s', fullPath='%s', children=%s, isFile=%b, isOpen=%b}",
name, path, fullPath, children, isFile, isOpen);
}
}