Android实现树形文件夹选择器

主要功能:显示系统存储,可设置自定义节点图标和文件夹图标以及大小,可多选,保留view重用机制,不会叠加内存,滑动不会卡顿。

示例代码:

mFolderTreeView = (FolderTreeView) findViewById(R.id.mFolderTreeView);
        findViewById(R.id.bn_ok).setOnClickListener(this);

        mFolderTreeView.setShowHidden(false); //不显示隐藏目录
        mFolderTreeView.setMultiSelect(true); //多选
        //mFolderTreeView.setLineSpace(16); //行间距
        //mFolderTreeView.setSelectedBkDrawable(R.drawable.item_selected); //选择项背景
        mFolderTreeView.setShowRootDir(true) //显示根目录
                .setShowPublicDir(true) //显示公共目录
                .setMultiSelect(true) //多选
                .setNodeIndent(10) //缩进
                .setShowHidden(true) //显示隐藏目录
                .setSortType(FolderTreeView.SORT_BY_NAME); //排序

        mFolderTreeView.setOnNodeClickListener(this); //目录点击监听
        mFolderTreeView.setOnNodeCheckedListener(this); //目录选择监听

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_STORAGE);
        } else {
            mFolderTreeView.init(); //最后初始化
        }
动画.gif

Screenshot_20210825-151357.jpg
/**
 * Created by yuanfang235 on 2021/8/24.
 * 文件夹树形View
 */
 
public class FolderTreeView extends FrameLayout {
 
    private static final String TAG = "fly";
 
    public static final int SORT_AUTO = 0;
    public static final int SORT_BY_NAME = 1;
    public static final int SORT_BY_TIME = 2;
 
    private RecyclerView mRecyclerView;
    private TreeAdapter mTreeAdapter;
    private List<Node> mNodes = new LinkedList<>();
    private LinearLayoutManager mLayoutManager;
    private OnNodeClickListener onNodeClickListener;
    private OnNodeCheckedListener onNodeCheckedListener;
    private float checkIconSize = 16;
    private float nodeIconSize = 12;
    private float folderIconSize = 24;
    private float nodeIndent = -1;
    private float lineSpace = 4;
    private float nodeTextSize = 14;
    private Drawable folderIcon;
    private Drawable nodeOpenIcon;
    private Drawable nodeCloseIcon;
    private Drawable nodeCheckedIcon;
    private Drawable nodeUncheckedIcon;
    private int sortType = SORT_BY_NAME;
    private boolean isSortDescending;
    private boolean isShowHidden = true;
    private boolean isShowRootDir;
    private boolean isShowPublicDir = true;
    private boolean isMultiSelect;
    private List<Node> mSelectedNodes = new LinkedList<>();
    private boolean isSelectedMode;
    private int selectedBkDrawable = 0;
 
    public FolderTreeView(Context context) {
        super(context);
        initView();
    }
 
    public FolderTreeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }
 
    public FolderTreeView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }
 
    private void initView() {
        setFocusable(true);
        setFocusableInTouchMode(true);
        nodeOpenIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_node_open);
        nodeCloseIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_node_close);
        nodeCheckedIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_checked);
        nodeUncheckedIcon = ContextCompat.getDrawable(getContext(), R.drawable.ic_unchecked);
        MyScrollView mScrollView = new MyScrollView(getContext());
        mScrollView.setFillViewport(true);
        addView(mScrollView);
        mRecyclerView = new MyRecyclerView(getContext());
        mRecyclerView.setVerticalScrollBarEnabled(true);
        mScrollView.addView(mRecyclerView);
        mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mTreeAdapter = new TreeAdapter();
        mRecyclerView.setAdapter(mTreeAdapter);
    }
 
    /**
     * 初始化或重置文件夹,获取存储权限后调用该方法
     *
     * @return
     */
    public boolean init() {
        if (!canReadStorage()) return false;
        mNodes.clear();
 
        if (isShowRootDir) {
            mNodes.add(makeNode(null, "根目录", new File("/")));
        }
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            mNodes.add(makeNode(null, "内部存储", Environment.getExternalStorageDirectory()));
        }
        List<File> dirList = getExternalSDCard(getContext().getApplicationContext());
        if (!dirList.isEmpty()) {
            File innerDir = Environment.getExternalStorageDirectory();
            for (int i = 0; i < dirList.size(); i++) {
                if (innerDir != null && dirList.get(i).getAbsolutePath().equals(innerDir.getAbsolutePath())) {
                    continue;
                }
                mNodes.add(makeNode(null, "扩展存储" + (i + 1), dirList.get(i)));
            }
        }
        if (isShowPublicDir && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            mNodes.add(makeNode(null, "下载", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)));
            mNodes.add(makeNode(null, "图片", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)));
            mNodes.add(makeNode(null, "相机", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)));
            mNodes.add(makeNode(null, "视频", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)));
            mNodes.add(makeNode(null, "音乐", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)));
            mNodes.add(makeNode(null, "文档", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)));
        }
        refresh();
        return true;
    }
 
    /**
     * 生成节点
     *
     * @param parent
     * @param dir
     * @return
     */
    private Node makeNode(Node parent, String name, File dir) {
        Node node = new Node(name, dir.getAbsolutePath(), dir.lastModified());
        if (parent != null) {
            node.level = parent.level + 1;
        }
        File[] files = dir.listFiles(mDirFilter);
        if (files != null && files.length > 0) {
            node.isShowNodeIcon = true;
        }
        return node;
    }
 
    /**
     * 目录过滤器
     */
    private FileFilter mDirFilter = new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            return pathname.isDirectory() && (isShowHidden || !pathname.isHidden());
        }
    };
 
    private class TreeAdapter extends RecyclerView.Adapter<TreeAdapter.MyViewHolder> {
 
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.tree_item_node, parent, false));
        }
 
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            final Node node = mNodes.get(position);
            holder.tv_name.setText(node.name);
            holder.tv_name.setTextSize(nodeTextSize);
            if (folderIcon != null) {
                holder.iv_icon.setImageDrawable(folderIcon);
            }
            if (isSelectedMode) {
                holder.iv_node.setVisibility(GONE);
                if (isMultiSelect || mSelectedNodes.contains(node)) {
                    holder.iv_check.setVisibility(VISIBLE);
                } else {
                    holder.iv_check.setVisibility(INVISIBLE);
                }
                if (mSelectedNodes.contains(node)) {
                    holder.iv_check.setImageDrawable(nodeCheckedIcon);
                    holder.vg_item.setBackgroundResource(selectedBkDrawable);
                } else {
                    holder.iv_check.setImageDrawable(nodeUncheckedIcon);
                    holder.vg_item.setBackgroundColor(0);
                }
            } else {
                holder.iv_node.setVisibility(node.isShowNodeIcon ? VISIBLE : INVISIBLE);
                holder.iv_check.setVisibility(GONE);
                holder.iv_check.setImageDrawable(null);
                holder.vg_item.setBackgroundColor(0);
            }
            holder.iv_node.setImageDrawable(node.isExpanded ? nodeOpenIcon : nodeCloseIcon);
            int padding = nodeIndent < 0 ? dip2px(folderIconSize) : dip2px(nodeIndent);
            holder.itemView.setPadding(padding * node.level + dip2px(10), dip2px(lineSpace) / 2, dip2px(10), dip2px(lineSpace) / 2);
            LinearLayout.LayoutParams fiParams = (LinearLayout.LayoutParams) holder.iv_icon.getLayoutParams();
            if (fiParams != null) {
                fiParams.width = dip2px(folderIconSize);
                fiParams.height = dip2px(folderIconSize);
                holder.iv_icon.requestLayout();
            }
            LinearLayout.LayoutParams niParams = (LinearLayout.LayoutParams) holder.iv_node.getLayoutParams();
            if (niParams != null) {
                niParams.width = dip2px(nodeIconSize);
                niParams.height = dip2px(nodeIconSize);
                holder.iv_node.requestLayout();
            }
            LinearLayout.LayoutParams ciParams = (LinearLayout.LayoutParams) holder.iv_check.getLayoutParams();
            if (ciParams != null) {
                ciParams.width = dip2px(checkIconSize);
                ciParams.height = dip2px(checkIconSize);
                holder.iv_check.requestLayout();
            }
            holder.itemView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (isSelectedMode) {
                        updateSelected(node, position, true);
                    } else {
                        if (node.isShowNodeIcon) {
                            node.isExpanded = !node.isExpanded;
                            updateNodeState(holder, node);
                            if (onNodeClickListener != null) {
                                onNodeClickListener.onNodeClick(FolderTreeView.this, position, new File(node.path), node.isExpanded);
                            }
                        }
                    }
                }
            });
            holder.itemView.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    updateSelected(node, position, false);
                    return true;
                }
            });
 
        }
 
        @Override
        public int getItemCount() {
            return mNodes == null ? 0 : mNodes.size();
        }
 
        class MyViewHolder extends RecyclerView.ViewHolder {
 
            ImageView iv_check;
            ImageView iv_node;
            ImageView iv_icon;
            TextView tv_name;
            ViewGroup vg_item;
 
            public MyViewHolder(View itemView) {
                super(itemView);
                iv_check = itemView.findViewById(R.id.iv_check);
                iv_node = itemView.findViewById(R.id.iv_node);
                iv_icon = itemView.findViewById(R.id.iv_icon);
                tv_name = itemView.findViewById(R.id.tv_name);
                vg_item = itemView.findViewById(R.id.vg_item);
            }
        }
    }
 
    private void updateSelected(Node node, int position, boolean isClick) {
        boolean isChecked;
        if (mSelectedNodes.contains(node)) {
            mSelectedNodes.remove(node);
            isChecked = false;
        } else {
            if (!isMultiSelect) {
                mSelectedNodes.clear();
            }
            mSelectedNodes.add(node);
            isChecked = true;
        }
        if (isClick && isMultiSelect) {
            isSelectedMode = true;
        } else {
            isSelectedMode = !mSelectedNodes.isEmpty();
        }
        refresh();
        if (onNodeCheckedListener != null) {
            onNodeCheckedListener.onNodeChecked(this, position, new File(node.path), isChecked);
        }
    }
 
    private void updateNodeState(TreeAdapter.MyViewHolder holder, Node node) {
        if (node.isExpanded) {
            startNodeOpenAnimation(holder.iv_node);
            addNode(node);
        } else {
            startNodeCloseAnimation(holder.iv_node);
            removeNode(node);
        }
    }
 
    /**
     * 添加节点
     *
     * @param parent
     */
    private void addNode(Node parent) {
        int position = mNodes.indexOf(parent);
        boolean isLast = (position == mNodes.size() - 1);
        File dir = new File(parent.path);
        File[] files = dir.listFiles(mDirFilter);
        if (files != null && files.length > 0) {
            List<Node> nodes = new ArrayList<>();
            for (File file : files) {
                nodes.add(makeNode(parent, file.getName(), file));
            }
            if (sortType != SORT_AUTO) {
                sort(nodes);
            }
            if (position < mNodes.size() - 1) {
                mNodes.addAll(position + 1, nodes);
            } else {
                mNodes.addAll(nodes);
            }
            parent.isShowNodeIcon = true;
            mTreeAdapter.notifyItemRangeInserted(position + 1, nodes.size());
            if (isLast) {
                mLayoutManager.scrollToPositionWithOffset(position, 0);
            }
        } else {
            parent.isShowNodeIcon = false;
            mTreeAdapter.notifyItemChanged(position);
        }
    }
 
    private void sort(List<Node> nodes) {
        final Collator collator = Collator.getInstance();
        Collections.sort(nodes, new Comparator<Node>() {
            @Override
            public int compare(Node o1, Node o2) {
                if (sortType == SORT_BY_NAME) {
                    return collator.compare(o1.name, o2.name);
                } else if (sortType == SORT_BY_TIME) {
                    if (o1.time < o2.time) {
                        return -1;
                    } else if (o1.time > o2.time) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
                return 0;
            }
        });
        if (isSortDescending) {
            Collections.reverse(nodes);
        }
    }
 
    private void startNodeOpenAnimation(final ImageView nodeView) {
        RotateAnimation animation = new RotateAnimation(0, 90, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(300);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
 
            }
 
            @Override
            public void onAnimationEnd(Animation animation) {
                nodeView.clearAnimation();
                nodeView.setImageDrawable(nodeOpenIcon);
            }
 
            @Override
            public void onAnimationRepeat(Animation animation) {
 
            }
        });
        nodeView.startAnimation(animation);
    }
 
    private void startNodeCloseAnimation(final ImageView nodeView) {
        RotateAnimation animation = new RotateAnimation(0, -90, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(300);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
 
            }
 
            @Override
            public void onAnimationEnd(Animation animation) {
                nodeView.clearAnimation();
                nodeView.setImageDrawable(nodeCloseIcon);
            }
 
            @Override
            public void onAnimationRepeat(Animation animation) {
 
            }
        });
        nodeView.startAnimation(animation);
    }
 
    /**
     * 删除节点
     *
     * @param parent
     */
    private void removeNode(Node parent) {
        List<Node> nodes = new ArrayList<>();
        int position = mNodes.indexOf(parent);
        for (int i = position + 1; i < mNodes.size(); i++) {
            Node node = mNodes.get(i);
            if (node.level > parent.level) {
                nodes.add(node);
            } else {
                break;
            }
        }
        if (!nodes.isEmpty()) {
            mNodes.removeAll(nodes);
            mTreeAdapter.notifyItemRangeRemoved(position + 1, nodes.size());
        }
    }
 
    private class Node {
        String name;
        String path;
        long time;
        int level;
        boolean isExpanded;
        boolean isShowNodeIcon;
 
        public Node(String name, String path, long time) {
            this.name = name;
            this.path = path;
            this.time = time;
        }
    }
 
    public void refresh() {
        if (mTreeAdapter != null) {
            mTreeAdapter.notifyDataSetChanged();
        }
    }
 
    /**
     * 存储权限
     *
     * @return
     */
    private boolean canReadStorage() {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
                || ContextCompat.checkSelfPermission(getContext().getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }
 
    /**
     * 节点点击事件
     *
     * @param onNodeClickListener
     * @return
     */
    public FolderTreeView setOnNodeClickListener(OnNodeClickListener onNodeClickListener) {
        this.onNodeClickListener = onNodeClickListener;
        return this;
    }
 
    /**
     * 节点选择事件
     *
     * @param onNodeCheckedListener
     * @return
     */
    public FolderTreeView setOnNodeCheckedListener(OnNodeCheckedListener onNodeCheckedListener) {
        this.onNodeCheckedListener = onNodeCheckedListener;
        return this;
    }
 
    public interface OnNodeClickListener {
        void onNodeClick(FolderTreeView view, int position, File dir, boolean isExpanded);
    }
 
    public interface OnNodeCheckedListener {
        void onNodeChecked(FolderTreeView view, int position, File dir, boolean isChecked);
    }
 
    /**
     * 设置check框大小
     *
     * @param checkIconSize
     * @return
     */
    public FolderTreeView setCheckIconSize(float checkIconSize) {
        this.checkIconSize = checkIconSize;
        refresh();
        return this;
    }
 
    /**
     * 设置节点按钮大小,单位dip
     *
     * @param nodeIconSize
     * @return
     */
    public FolderTreeView setNodeIconSize(float nodeIconSize) {
        this.nodeIconSize = nodeIconSize;
        refresh();
        return this;
    }
 
    /**
     * 设置文件夹图标大小,单位dip
     *
     * @param folderIconSize
     * @return
     */
    public FolderTreeView setFolderIconSize(float folderIconSize) {
        this.folderIconSize = folderIconSize;
        refresh();
        return this;
    }
 
    /**
     * 设置缩进,单位dip
     *
     * @param nodeIndent
     * @return
     */
    public FolderTreeView setNodeIndent(float nodeIndent) {
        this.nodeIndent = nodeIndent;
        refresh();
        return this;
    }
 
    private int dip2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
 
    /**
     * 设置行间距,单位dip
     *
     * @param lineSpace
     * @return
     */
    public FolderTreeView setLineSpace(float lineSpace) {
        this.lineSpace = lineSpace;
        refresh();
        return this;
    }
 
    /**
     * 设置节点文字大小,单位sp
     *
     * @param nodeTextSize
     */
    public FolderTreeView setNodeTextSize(float nodeTextSize) {
        this.nodeTextSize = nodeTextSize;
        refresh();
        return this;
    }
 
    /**
     * 文件夹图标
     *
     * @param iconResId
     * @return
     */
    public FolderTreeView setFolderIcon(int iconResId) {
        folderIcon = ContextCompat.getDrawable(getContext(), iconResId);
        refresh();
        return this;
    }
 
    /**
     * 文件夹图标
     *
     * @param icon
     * @return
     */
    public FolderTreeView setFolderIcon(Drawable icon) {
        folderIcon = icon;
        refresh();
        return this;
    }
 
    /**
     * 排序方式, 初始化之前执行
     *
     * @param sortType
     */
    public FolderTreeView setSortType(int sortType) {
        this.sortType = sortType;
        return this;
    }
 
    /**
     * 降序排序, 初始化之前执行
     *
     * @param sortDescending
     */
    public FolderTreeView setSortDescending(boolean sortDescending) {
        isSortDescending = sortDescending;
        return this;
    }
 
    /**
     * 显示隐藏目录, 初始化之前执行
     *
     * @param showHidden
     */
    public FolderTreeView setShowHidden(boolean showHidden) {
        isShowHidden = showHidden;
        return this;
    }
 
 
    /**
     * 设置节点图标
     *
     * @param nodeOpenIcon
     * @param nodeCloseIcon
     */
    public FolderTreeView setNodeIcon(Drawable nodeOpenIcon, Drawable nodeCloseIcon) {
        this.nodeOpenIcon = nodeOpenIcon;
        this.nodeCloseIcon = nodeCloseIcon;
        refresh();
        return this;
    }
 
    /**
     * 设置节点图标
     *
     * @param nodeOpenIcon
     * @param nodeCloseIcon
     */
    public FolderTreeView setNodeIcon(int nodeOpenIcon, int nodeCloseIcon) {
        return setNodeIcon(ContextCompat.getDrawable(getContext(), nodeOpenIcon),
                ContextCompat.getDrawable(getContext(), nodeCloseIcon));
    }
 
    /**
     * 设置节点选择图标
     * @param checked
     * @param unchecked
     * @return
     */
    public FolderTreeView setNodeCheckIcon(Drawable checked, Drawable unchecked) {
        this.nodeCheckedIcon = checked;
        this.nodeUncheckedIcon = unchecked;
        return this;
    }
 
    /**
     * 设置节点选择图标
     * @param checked
     * @param unchecked
     * @return
     */
    public FolderTreeView setNodeCheckIcon(int checked, int unchecked) {
        return setNodeCheckIcon(ContextCompat.getDrawable(getContext(), checked),
                ContextCompat.getDrawable(getContext(), unchecked));
    }
 
    /**
     * 显示根目录
     *
     * @param showRootDir
     */
    public FolderTreeView setShowRootDir(boolean showRootDir) {
        isShowRootDir = showRootDir;
        return this;
    }
 
    /**
     * 显示公共目录
     *
     * @param showPublicDir
     */
    public FolderTreeView setShowPublicDir(boolean showPublicDir) {
        isShowPublicDir = showPublicDir;
        return this;
    }
 
    /**
     * 设置多选模式
     *
     * @param multiSelect
     */
    public FolderTreeView setMultiSelect(boolean multiSelect) {
        isMultiSelect = multiSelect;
        return this;
    }
 
    /**
     * 是选择模式
     *
     * @return
     */
    public boolean isSelectedMode() {
        return isSelectedMode;
    }
 
    /**
     * 获取选择的目录列表
     *
     * @return
     */
    public List<File> getSelectedFolderList() {
        List<File> dirList = new ArrayList<>();
        for (Node mSelectedNode : mSelectedNodes) {
            dirList.add(new File(mSelectedNode.path));
        }
        return dirList;
    }
 
    /**
     * 获取单选目录
     *
     * @return
     */
    public File getSelectedFolder() {
        if (isMultiSelect || mSelectedNodes.isEmpty()) {
            return null;
        } else {
            return new File(mSelectedNodes.get(0).path);
        }
    }
 
    /**
     * 选定背景
     *
     * @param selectedBkDrawable
     */
    public FolderTreeView setSelectedBkDrawable(int selectedBkDrawable) {
        this.selectedBkDrawable = selectedBkDrawable;
        return this;
    }
 
    static class MyScrollView extends HorizontalScrollView {
 
        public MyScrollView(Context context) {
            super(context);
        }
 
        public MyScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
 
        public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
 
        private float lastX, lastY;
 
        @Override
        public boolean onInterceptTouchEvent(MotionEvent e) {
 
            boolean intercept = super.onInterceptTouchEvent(e);
 
            switch (e.getAction() & e.getActionMasked()) {
 
                case MotionEvent.ACTION_DOWN:
                    lastX = e.getX();
                    lastY = e.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float x = Math.abs(e.getX() - lastX);
                    float y = Math.abs(e.getY() - lastY);
                    if ((x > 0 || y > 0) && y >= x) {
                        requestDisallowInterceptTouchEvent(true);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    intercept = false;
                    break;
            }
            return intercept;
        }
 
    }
 
    private static class MyRecyclerView extends RecyclerView {
 
        public MyRecyclerView(Context context) {
            super(context);
        }
 
        public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
 
        public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
 
    }
 
    private static void print(Object... info) {
        if (info == null || info.length == 0) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        for (Object o : info) {
            sb.append(o != null ? o.toString() + ", " : ", ");
        }
        String msg = sb.toString();
        if (msg.endsWith(",")) msg = sb.deleteCharAt(sb.length() - 1).toString();
        Log.e(TAG, msg);
    }
 
    private static List<File> getExternalSDCard(Context context) {
        List<File> pathsList = new ArrayList<>();
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        try {
            Method method = StorageManager.class.getDeclaredMethod("getVolumePaths");
            method.setAccessible(true);
            Object result = method.invoke(storageManager);
            if (result != null && result instanceof String[]) {
                String[] pathes = (String[]) result;
                if (pathes != null && pathes.length > 0) {
                    StatFs statFs;
                    for (String path : pathes) {
                        File file = new File(path);
                        if (!android.text.TextUtils.isEmpty(path) && file.exists()) {
                            statFs = new StatFs(path);
                            if (statFs.getBlockCount() * statFs.getBlockSize() != 0) {
                                pathsList.add(file);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pathsList;
    }
 
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
            if (isSelectedMode) {
                cancelSelectedMode();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
 
    private void cancelSelectedMode() {
        mSelectedNodes.clear();
        isSelectedMode = false;
        refresh();
    }
}

库文件和demo下载地址:https://github.com/yuanfang235/FolderTreeView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容