recyclerview中树状结构的实现,加载本地中文件夹信息
引文:
在项目实现中,对于树状图结构的分析一直无法实现正确的效果,结果查看别人的项目都不要适合我的应用场景,但是查看其实原理是差不多的,但是我没有看明白,所以一直在看这方面的东西。查阅并修改他人的代码实现文件夹效果,看着还是有点繁琐,先记录,后续再进行数据的修改。声明一点,我的代码是在别人的代码的基础上进行修改。
-
效果呈现
-
系统讲解
- 需要的控件:recyclerview(其实使用Listview也可以,就是adapter中结构不一样)
- 实现步骤
(1)数据初始化
(2)点击监听,数据更新
(3)数据处理,构建树状结构
-
具体的代码实现如下所示:
1、在activity或者在fragment中进行数据的初始化和数据更新
private void getLocalData() {//获取本地路径List<FileNodeBean> fileList = new ArrayList<>();//获取本地文件目录File path = Environment.getExternalStorageDirectory();Log.d(TAG, "onClick: " + path.getName());//获取文件子项File[] mfile = path.listFiles();List<File> mAllLocalData = new ArrayList<>();//目录初始化for (int i = 0 ; i < mfile.length; i++) {//本地文件中存在隐藏的文件,不需要显示if (!mfile[i].isHidden()) {if (MineTypeUtil.isShowFile(mfile[i])){FileNodeBean fileNode = new FileNodeBean("local" + i, 0, mfile[i].getName());fileNode.setParent(null);fileNode.setLevel(0);fileNode.setId(mfile[i].getPath());fileNode.setPath(mfile[i].getPath());fileNode.setVidelFile(MineTypeUtil.isVideoFile(mfile[i].getPath()));fileList.add(fileNode);mAllLocalData.add(mfile[i]);}}}//适配adapter,初始化数据mFileAdapter = new FileAdapter(mFileRecyclerView, mContext);mFileAdapter.updateData(fileList, 0);mFileRecyclerView.setAdapter(mFileAdapter);//点击图标的监听事件mFileAdapter.setNodeDataListener(new FileAdapter.onNodeDataListener() {@Overridepublic void onNodeDataListener(FileNodeBean node) {Log.d(TAG, "onNodeDataListener: + getName " + node.getName());List<FileNodeBean> childList = new ArrayList<>();for (int i = 0 ; i < mAllLocalData.size(); i++) {//通过路径判断获取点击的itemif (mAllLocalData.get(i).getPath().equals(node.getPath())) {Log.d(TAG, "onNodeDataListener:node.path " + node.getPath());if (mAllLocalData.get(i).listFiles() != null){//获取是否存在子项Log.d(TAG, "onNodeDataListener:listfile " + mAllLocalData.get(i).listFiles());int m = 0;//遍历点击item的子项for (int j = 0; j < mAllLocalData.get(i).listFiles().length; j++) {//子项为目录或者音频文件时if (MineTypeUtil.isShowFile(mAllLocalData.get(i).listFiles()[j])){m++;FileNodeBean fileNodeBean = new FileNodeBean();fileNodeBean.setId(mAllLocalData.get(i).listFiles()[j].getPath());fileNodeBean.setName(mAllLocalData.get(i).listFiles()[j].getName());fileNodeBean.setExpand(false);fileNodeBean.setParent(node);fileNodeBean.setPath(mAllLocalData.get(i).listFiles()[j].getPath());fileNodeBean.setVidelFile(MineTypeUtil.isVideoFile(mAllLocalData.get(i).listFiles()[j].getPath()));fileNodeBean.setLevel(node.getLevel() + 1);childList.add(fileNodeBean);//更新列表if (node.isExpand()){mAllLocalData.remove(mAllLocalData.get(i).listFiles()[j]);}else {mAllLocalData.add(i + m, mAllLocalData.get(i).listFiles()[j]);}}}//更新fieList的数据for(int k = 0; k < fileList.size(); k++){if (fileList.get(k).getPath().equals(node.getPath())) {fileList.get(k).setChildren(childList);break;}}//对于数据展开与闭合的处理if (node.isExpand()) {childList.clear();node.setChildren(childList);node.setExpand(!node.isExpand());mFileAdapter.updateData(fileList, node.getLevel() - 1);} else {node.setChildren(childList);node.setExpand(!node.isExpand());mFileAdapter.updateData(fileList, node.getLevel() + 1);}}else {Log.d(TAG, "onNodeDataListener: " + mAllLocalData.get(i).listFiles());Toast.makeText(mContext, "子项为空", Toast.LENGTH_SHORT).show();}break;}}}});}
- recyclerview中adapter处理,继承于TreeListViewAdapter ,将一些通用的数据进行处理
public class FileAdapter extends TreeListViewAdapter {private static final String TAG = " TreeListViewAdapter ";private onNodeDataListener onNodeDataListener;private FileAdapter.onItemDataListener onItemDataListener ;public FileAdapter(RecyclerView mTree, Context context) {super(mTree, context);}private List<FileNodeBean> mDatas= new ArrayList<>();//数据更新,每一次点击之后,数据展开关闭产生的数据变化@Overridepublic void updateData(List<FileNodeBean> datas, int defaultExpandLevel) {super.updateData(datas, defaultExpandLevel);}@Overrideprotected void getBindViewHolder(FileNodeBean node, RecyclerView.ViewHolder holder, int position) {ViewHolder mHolder = (ViewHolder)holder;if (node.isVidelFile()){mHolder.typeImage.setImageResource(R.mipmap.icon_file_manager_video);mHolder.mExpandImage.setVisibility(View.GONE);}else {mHolder.typeImage.setImageResource(R.mipmap.icon_file_manager_folder);mHolder.mExpandImage.setVisibility(View.VISIBLE);}mHolder.mExpandImage.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {expandOrCollapse(position);// setChecked(node, node.isExpand());onNodeDataListener.onNodeDataListener(node);notifyDataSetChanged();}});holder.itemView.setSelected(node.isSelelct());if (node.isExpand()){mHolder.mExpandImage.setBackgroundResource(R.mipmap.icon_file_folder_expand);}else {mHolder.mExpandImage.setBackgroundResource(R.mipmap.icon_file_folder_unexpand);}mHolder.tvName.setText(node.getName());//增加一个item的点击事件,修改item的状态holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {for (int i = 0 ; i < mNodes.size(); i++){if (node.getId().equals(mNodes.get(i).getId())){}else {mNodes.get(i).setSelelct(false);Log.d("lucky", "onClick: " + mNodes.get(i).isSelelct());}}Log.d("lucky", "onClick: " + node.isSelelct());onItemDataListener.onItemDataListener(node);notifyDataSetChanged();}});}@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = mInflater.inflate(R.layout.file_move_folder_path_item, parent, false);RecyclerView.ViewHolder holder = new ViewHolder(view);view.setTag(holder);return holder;}class ViewHolder extends RecyclerView.ViewHolder{TextView tvName;ImageView mExpandImage;ImageView typeImage;public ViewHolder(@NonNull View itemView) {super(itemView);tvName = itemView.findViewById(R.id.folder_name);mExpandImage = itemView.findViewById(R.id.expand_change_image);typeImage = itemView.findViewById(R.id.folder_image);}}public void setNodeDataListener(onNodeDataListener setNodeDataListener) {this.onNodeDataListener = setNodeDataListener;}public void setSelelctDataListener(onItemDataListener itemDataListener) {this.onItemDataListener = itemDataListener;}public interface onNodeDataListener {void onNodeDataListener(FileNodeBean node);}public interface onItemDataListener {void onItemDataListener(FileNodeBean node);}
}
2、TreeListViewAdapter 表示通用结构中的实现:
public abstract class TreeListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private static final String TAG = " TreeListViewAdapter ";protected Context mContext;/* 存储所有可见的Node*/protected List<FileNodeBean> mNodes = new ArrayList<>();protected LayoutInflater mInflater;/* 存储所有的Node*/protected List<FileNodeBean> mAllNodes = new ArrayList<>();public TreeListViewAdapter(RecyclerView mTree, Context context){mContext = context;mInflater = LayoutInflater.from(context);}//上一个状态的所有nodeprivate List<FileNodeBean> beforeDatas = new ArrayList<>();public void updateData(List<FileNodeBean> datas, int defaultExpandLevel) {/* 对所有的Node进行排序*/mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);/* 过滤出可见的Node*/mNodes = TreeHelper.filterVisibleNode(mAllNodes);Log.d("TAG", "updateData: " + mNodes.size() + " " + mAllNodes.size());}/* 相应ListView的点击事件 展开或关闭某节点*/public void expandOrCollapse(int position) {FileNodeBean n = mNodes.get(position);if (n != null) {// 排除传入参数错误异常//item不为叶子节点if (!n.isLeaf()) {//n.setExpand(!n.isExpand());mNodes = TreeHelper.filterVisibleNode(mAllNodes);notifyDataSetChanged();// 刷新视图}}}@Overridepublic int getItemCount() {return mNodes.size();}@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {FileNodeBean nodeBean = mNodes.get(position);holder.itemView.setPadding((nodeBean.getLevel()+1) * 40, 3, 3, 3);getBindViewHolder(nodeBean, holder, position);}protected abstract void getBindViewHolder(FileNodeBean node, RecyclerView.ViewHolder holder, int position);@Overridepublic long getItemId(int position) {return position;}}
3、MineTypeUtil 用于判断文件的类型
public class MineTypeUtil {private static final char SEPARATOR = '/';private static final char EXTENSION_SEPARATOR = '.';public static final String GENERIC_MIME_TYPE = "application/octet-stream";private static final Map<String, String> sExtensionToMimeTypeMap =MapBuilder.<String, String>newHashMap()// Compatibility (starting from L), in the order they appear in Android source..put("mp3", "application/mp3").put("wav", "application/wav").put("gsm", "application/gsm").put("text", "application/text").buildUnmodifiable();private static final String TAG = "MineTypeUtil";public static String getMimeType(@NonNull String path) {String extension = getExtension(path);extension = extension.toLowerCase(Locale.US);String mimeType = sExtensionToMimeTypeMap.get(extension);if (!TextUtils.isEmpty(mimeType)) {return mimeType;}mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);if (!TextUtils.isEmpty(mimeType)) {return mimeType;}return GENERIC_MIME_TYPE;}public static String getExtension(@NonNull String path) {int index = indexOfExtensionSeparator(path);if (path == null){index = -1;}return index != -1 ? path.substring(index + 1) : "";}public static int indexOfExtensionSeparator(@NonNull String path) {int lastSeparatorIndex = indexOfLastSeparator(path);if (path == null){return -1;}int lastExtensionSeparatorIndex = path.lastIndexOf(EXTENSION_SEPARATOR);return lastSeparatorIndex > lastExtensionSeparatorIndex ? -1 : lastExtensionSeparatorIndex;}public static int indexOfLastSeparator(@NonNull String path) {if (path == null){return -1;}return path.lastIndexOf(SEPARATOR);}public static boolean isShowFile(File file) {String path = file.getPath();boolean isVideo = false;isVideo = isVideoFile(path);if (!file.isHidden() && (isVideo || file.isDirectory())) {return true;}return false;}public static boolean isVideoFile(String path){if (MineTypeUtil.getMimeType(path).equals("application/mp3") || MineTypeUtil.getMimeType(path).equals("application/wav")|| MineTypeUtil.getMimeType(path).equals("application/gsm")) {return true;}return false;}
}
用于辅助处理MineTypeUtil 的文件信息
public class MapBuilder<K, V, M extends Map<K, V>> {@NonNullprivate M mMap;private MapBuilder(@NonNull M map) {mMap = map;}@NonNullpublic static <K, V> MapBuilder<K, V, HashMap<K, V>> newHashMap() {return new MapBuilder<>(new HashMap<>());}@NonNullpublic static <K, V, M extends Map<K, V>> MapBuilder<K, V, M> buildUpon(@NonNull M map) {return new MapBuilder<>(map);}@NonNullpublic M build() {M map = mMap;mMap = null;return map;}@NonNullpublic Map<K, V> buildUnmodifiable() {Map<K, V> map = Collections.unmodifiableMap(mMap);mMap = null;return map;}@NonNullpublic MapBuilder<K, V, M> put(@Nullable K key, @Nullable V value) {mMap.put(key, value);return this;}@NonNullpublic MapBuilder<K, V, M> remove(@Nullable K key) {mMap.remove(key);return this;}@NonNullpublic MapBuilder<K, V, M> putAll(@NonNull Map<? extends K, ? extends V> m) {mMap.putAll(m);return this;}@NonNullpublic MapBuilder<K, V, M> clear() {mMap.clear();return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> replaceAll(@NonNull BiFunction<? super K, ? super V, ? extends V> function) {mMap.replaceAll(function);return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> putIfAbsent(@Nullable K key, @Nullable V value) {mMap.putIfAbsent(key, value);return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> remove(@Nullable K key, @Nullable V value) {mMap.remove(key, value);return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> replace(@Nullable K key, @Nullable V oldValue,@Nullable V newValue) {mMap.replace(key, oldValue, newValue);return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> replace(@Nullable K key, @Nullable V value) {mMap.replace(key, value);return this;}@NonNull@RequiresApi(Build.VERSION_CODES.N)public MapBuilder<K, V, M> merge(@Nullable K key, @NonNull V value,@NonNull BiFunction<? super V, ? super V, ? extends V> remappingFunction) {mMap.merge(key, value, remappingFunction);return this;}
}
4、构建树状图的关键处理
public class TreeHelper {/* 传入node 返回排序后的Node*/public static List<FileNodeBean> getSortedNodes(List<FileNodeBean> datas,int defaultExpandLevel) {List<FileNodeBean> result = new ArrayList<FileNodeBean>();// 设置Node间父子关系List<FileNodeBean> nodes = convetData2Node(datas);// 拿到根节点List<FileNodeBean> rootNodes = getRootNodes(nodes);// 排序以及设置Node间关系for (FileNodeBean node : rootNodes) {addNode(result, node, defaultExpandLevel);}return result;}/* 过滤出所有可见的Node*/public static List<FileNodeBean> filterVisibleNode(List<FileNodeBean> nodes) {List<FileNodeBean> result = new ArrayList<FileNodeBean>();for (FileNodeBean node : nodes) {// 如果为根节点,或者上层目录为展开状态if (node.isRoot() || node.isParentExpand()) {result.add(node);}}return result;}/* 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系*/private static List<FileNodeBean> convetData2Node(List<FileNodeBean> nodes) {for (int i = 0; i < nodes.size(); i++) {FileNodeBean n = nodes.get(i);for (int j = i + 1; j < nodes.size(); j++) {FileNodeBean m = nodes.get(j);//id的类型为string类型if (m.getpId() instanceof String) {if (m.getpId().equals(n.getId())) {n.getChildren().add(m);m.setParent(n);} else if (m.getId().equals(n.getpId())) {m.getChildren().add(n);n.setParent(m);}} else {//id类型为int型if (m.getpId() == n.getId()) {n.getChildren().add(m);m.setParent(n);} else if (m.getId() == n.getpId()) {m.getChildren().add(n);n.setParent(m);}}}}return nodes;}// 拿到根节点private static List<FileNodeBean> getRootNodes(List<FileNodeBean> nodes) {List<FileNodeBean> root = new ArrayList<FileNodeBean>();for (FileNodeBean node : nodes) {if (node.getLevel() == 0 || node.isRoot()){root.add(node);}
// if (node.isRoot())
// root.add(node);}return root;}/* 递归,把一个节点上的所有的内容都挂上去* @param nodes node为挂载后的数据* @param node 需要增加的子项* @param currentLevel* @param <T>* @param <B>*/private static <T, B> void addNode(List<FileNodeBean> nodes, FileNodeBean<T, B> node,int currentLevel) {//将所有node加入到字符中node.setLevel(currentLevel);nodes.add(node);if (node.isLeaf()){return;}for (int i = 0 ; i < node.getChildren().size(); i++){addNode(nodes, node.getChildren().get(i), currentLevel + 1);}Log.d("Treehelper", "addNode: " + nodes.size());}
}
辅助处理事件
public class FileNodeBean<T, B> {/* 分级树状等字段*///传入的实体对象public B bean;//父级id子级pidprivate T id;//根节点pId为0private T pId;//节点名称private String name;//当前的级别private int level;//是否展开private boolean isExpand = false;//子节点数据 ,套用当前nodeprivate List<FileNodeBean> children = new ArrayList<>();//父节点private FileNodeBean parent;//是否被checked选中private boolean isChecked;private boolean isSelelct;private String path;private boolean isVidelFile;public FileNodeBean() {}public FileNodeBean(T id, T pId, String name) {super();this.id = id;this.pId = pId;this.name = name;}public FileNodeBean(T id, T pId, String name, B bean) {super();this.id = id;this.pId = pId;this.name = name;this.bean = bean;}public boolean isVidelFile() {return isVidelFile;}public void setVidelFile(boolean videlFile) {isVidelFile = videlFile;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public boolean isSelelct() {return isSelelct;}public void setSelelct(boolean selelct) {isSelelct = selelct;}public boolean isChecked() {return isChecked;}public void setChecked(boolean isChecked) {this.isChecked = isChecked;}public T getId() {return id;}public void setId(T id) {this.id = id;}public T getpId() {return pId;}public void setpId(T pId) {this.pId = pId;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void setLevel(int level) {this.level = level;}public boolean isExpand() {return isExpand;}public List<FileNodeBean> getChildren() {return children;}public void setChildren(List<FileNodeBean> children) {this.children = children;}public FileNodeBean getParent() {return parent;}public void setParent(FileNodeBean parent) {this.parent = parent;if (parent != null){this.pId = (T) parent.getId();}}/* 是否为跟节点*/public boolean isRoot() {return parent == null;}/* 判断父节点是否展开*/public boolean isParentExpand() {if (parent == null)return false;return parent.isExpand();}/* 是否是叶子界点*/public boolean isLeaf() {return children.size() == 0;}/* 获取当前所在层级,* 设置层级间隔*/public int getLevel() {return parent == null ? 0 : parent.getLevel() + 1;}/* 设置展开*/public void setExpand(boolean isExpand) {this.isExpand = isExpand;if (!isExpand) {for (FileNodeBean node : children) {node.setExpand(isExpand);}}}}