Задать вопрос
@Xedoxishe
Джуниор разработчик на java, android.

Пишу дерево файлов для Android (RecyclerView), Как пофиксить баг ниже?

Всем привет. Я ~неделю назад захотел написать дерево файлов под Андроид на основе RecyclerView. Собственно я написал все кроме openFolder/closeFolder за день. Остальное время я пытался написать эти функции... И все это время, меня преследовал только 1 баг :(
Он ниже:
67ad533a38ddb809438463.png
67ad543813445664583271.png

Адаптер:
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);
    }
}
  • Вопрос задан
  • 56 просмотров
Подписаться 1 Простой 10 комментариев
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы