//1. The first way to customize the adapter implementation:
1. The first step is to import the required dependent libraries:
//RecyclerView implementation 'com.android.support:recyclerview-v7:28.0.0'
//RecyclerAdapter implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28'
// refresh control implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-28'
2. The second step is to create a new BaseTreeRecyclerViewAdapter class:
/** * @author CJF */ public abstract class BaseTreeRecyclerViewAdapter extends RecyclerView.Adapter { protected Context mContext; /** * Default not expanded */ private int defaultExpandLevel = 0; /** * Expanded and closed images */ private int iconExpand = -1, iconNoExpand = -1; /** * Store all Nodes */ protected List<NodeBean> mAllNodeBeans = new ArrayList<>(); /** * Store all visible Nodes */ protected List<NodeBean> mNodeBeans = new ArrayList<>(); protected LayoutInflater mInflater; /** * Click callback interface */ private OnTreeClickListener treeClickListener; public interface OnTreeClickListener { void onClick(NodeBean nodeBean, int position); } /** * Interface monitoring * * @param treeClickListener */ public void setOnTreeClickListener(OnTreeClickListener treeClickListener) { this.treeClickListener = treeClickListener; } /** * Interface call * * @param nodeBean * @param position */ public void onTreeClickListener(NodeBean nodeBean, int position) { if (null != treeClickListener) { treeClickListener.onClick(nodeBean, position); } } public BaseTreeRecyclerViewAdapter(RecyclerView recyclerView, Context context, List<NodeBean> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) { mContext = context; this.defaultExpandLevel = defaultExpandLevel; this.iconExpand = iconExpand; this.iconNoExpand = iconNoExpand; for (NodeBean nodeBean : datas) { nodeBean.getChildren().clear(); nodeBean.iconExpand = iconExpand; nodeBean.iconNoExpand = iconNoExpand; } /** * Sort all Nodes */ mAllNodeBeans = TreeHelper.getInstance().getSortedNodes(datas, defaultExpandLevel); /** * Filter out visible Nodes */ mNodeBeans = TreeHelper.getInstance().filterVisibleNode(mAllNodeBeans); mInflater = LayoutInflater.from(context); } /** * @param mTree * @param context * @param dates * @param defaultExpandLevel 默认展开几级树 */ public BaseTreeRecyclerViewAdapter(RecyclerView mTree, Context context, List<NodeBean> datas, int defaultExpandLevel) { this(mTree, context, datas, defaultExpandLevel, -1, -1); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { NodeBean nodeBean = mNodeBeans.get(position); // convertView = getConvertView(node, position, convertView, parent); // 设置内边距 holder.itemView.setPadding(nodeBean.getLevel() * 50, 10, 10, 10); /** * 设置节点点击时,可以展开以及关闭,将事件继续往外公布 */ holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { expandOrCollapse(position); //接口调用 onTreeClickListener(mNodeBeans.get(position), position); } }); onBindViewHolder(nodeBean, holder, position); } @Override public int getItemCount() { return mNodeBeans.size(); } /** * Get all nodes after sorting * * @return */ public List<NodeBean> getAllNodes() { if (mAllNodeBeans == null) { mAllNodeBeans = new ArrayList<NodeBean>(); } return mAllNodeBeans; } /** * Get all selected nodes * * @return */ public List<NodeBean> getSelectedNode() { List<NodeBean> checks = new ArrayList<NodeBean>(); for (int i = 0; i < mAllNodeBeans.size(); i++) { NodeBean n = mAllNodeBeans.get(i); if (n.isChecked()) { checks.add(n); } } return checks; } /** * The click event of the corresponding ListView expands or closes a node * * @param position */ public void expandOrCollapse(int position) { NodeBean n = mNodeBeans.get(position); // 排除传入参数错误异常 if (n != null) { if (!n.isLeaf()) { n.setExpand(!n.isExpand()); mNodeBeans = TreeHelper.getInstance().filterVisibleNode(mAllNodeBeans); // 刷新视图 notifyDataSetChanged(); } } } /** * 设置多选 * * @param nodeBean * @param checked */ protected void setChecked(final NodeBean nodeBean, boolean checked) { nodeBean.setChecked(checked); setChildChecked(nodeBean, checked); if (nodeBean.getParent() != null) { setNodeParentChecked(nodeBean.getParent(), checked); } notifyDataSetChanged(); } /** * 设置是否选中 * * @param nodeBean * @param checked */ public <T> void setChildChecked(NodeBean<T> nodeBean, boolean checked) { if (!nodeBean.isLeaf()) { nodeBean.setChecked(checked); for (NodeBean childrenNodeBean : nodeBean.getChildren()) { setChildChecked(childrenNodeBean, checked); } } else { nodeBean.setChecked(checked); } } private void setNodeParentChecked(NodeBean nodeBean, boolean checked) { if (checked) { nodeBean.setChecked(checked); if (nodeBean.getParent() != null) { setNodeParentChecked(nodeBean.getParent(), checked); } } else { List<NodeBean> childrens = nodeBean.getChildren(); boolean isChecked = false; for (NodeBean children : childrens) { if (children.isChecked()) { isChecked = true; } } //If all self nodes are not selected, the parent node is not selected if (!isChecked) { nodeBean.setChecked(checked); } if (nodeBean.getParent() != null) { setNodeParentChecked(nodeBean.getParent(), checked); } } } /** * Clear the previous data and refresh and re-add * * @param mlists * @param defaultExpandLevel expand the list by default */ public void addDataAll(List<NodeBean> mlists, int defaultExpandLevel) { mAllNodeBeans.clear(); addData(-1, mlists, defaultExpandLevel); } /** * Add data at the specified location and refresh to specify the display level after refreshing * * @param index * @param mlists * @param defaultExpandLevel expand the list by default */ public void addData(int index, List<NodeBean> mlists, int defaultExpandLevel) { this.defaultExpandLevel = defaultExpandLevel; notifyData(index, mlists); } /** * Add data at the specified location and refresh * * @param index * @param mlists */ public void addData(int index, List<NodeBean> mlists) { notifyData(index, mlists); } /** * Add data and refresh * * @param mlists */ public void addData(List<NodeBean> mlists) { addData(mlists, defaultExpandLevel); } /** * Add data and refresh to specify the display level after refreshing * * @param mlists * @param defaultExpandLevel */ public void addData(List<NodeBean> mlists, int defaultExpandLevel) { this.defaultExpandLevel = defaultExpandLevel; notifyData(-1, mlists); } /** * Add data and refresh * * @param nodeBean */ public void addData(NodeBean nodeBean) { addData(nodeBean, defaultExpandLevel); } /** * Add data and refresh to specify the display level after refreshing * * @param nodeBean * @param defaultExpandLevel */ public void addData(NodeBean nodeBean, int defaultExpandLevel) { List<NodeBean> nodeBeans = new ArrayList<>(); nodeBeans.add(nodeBean); this.defaultExpandLevel = defaultExpandLevel; notifyData(-1, nodeBeans); } /** * Refresh data * * @param index * @param mListNodeBeans */ private void notifyData(int index, List<NodeBean> mListNodeBeans) { for (int i = 0; i < mListNodeBeans.size(); i++) { NodeBean nodeBean = mListNodeBeans.get(i); nodeBean.getChildren().clear(); nodeBean.iconExpand = iconExpand; nodeBean.iconNoExpand = iconNoExpand; } for (int i = 0; i < mAllNodeBeans.size(); i++) { NodeBean nodeBean = mAllNodeBeans.get(i); nodeBean.getChildren().clear(); // node.isNewAdd = false; } if (index != -1) { mAllNodeBeans.addAll(index, mListNodeBeans); } else { mAllNodeBeans.addAll(mListNodeBeans); } /** * Sort all Nodes */ mAllNodeBeans = TreeHelper.getInstance().getSortedNodes(mAllNodeBeans, defaultExpandLevel); /** * Filter out visible Nodes */ mNodeBeans = TreeHelper.getInstance().filterVisibleNode(mAllNodeBeans); // refresh data notifyDataSetChanged(); } public abstract void onBindViewHolder(NodeBean nodeBean, RecyclerView.ViewHolder holder, final int position); }
3. The third step is to create a new TreeRecyclerViewAdapter class:
/** * @author CJF */ public class TreeRecyclerViewAdapter extends BaseTreeRecyclerViewAdapter { //Selected callback interface private OnTreeCheckedChangeListener checkedChangeListener; public interface OnTreeCheckedChangeListener { void onCheckChange(NodeBean nodeBean, int position, boolean isChecked); } /** * Interface monitoring * * @param checkedChangeListener */ public void setTreeCheckedChangeListener(OnTreeCheckedChangeListener checkedChangeListener) { this.checkedChangeListener = checkedChangeListener; } /** * Interface call * * @param nodeBean * @param position * @param isChecked */ public void onTreeCheckedChangeListener(NodeBean nodeBean, int position, boolean isChecked) { if (null != checkedChangeListener) { checkedChangeListener.onCheckChange(nodeBean, position, isChecked); } } public TreeRecyclerViewAdapter(RecyclerView recyclerView, Context context, List<NodeBean> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) { super(recyclerView, context, datas, defaultExpandLevel, iconExpand, iconNoExpand); } public TreeRecyclerViewAdapter(RecyclerView mTree, Context context, List<NodeBean> datas, int defaultExpandLevel) { super(mTree, context, datas, defaultExpandLevel); } @Override public void onBindViewHolder(final NodeBean nodeBean, final RecyclerView.ViewHolder holder, final int position) { final ViewHolder viewHolder = (ViewHolder) holder; viewHolder.mTreeHeaderName.setText(nodeBean.getName()); if (nodeBean.getIcon() == -1) { viewHolder.mTreeHeaderExpand.setVisibility(View.INVISIBLE); } else { viewHolder.mTreeHeaderExpand.setVisibility(View.VISIBLE); viewHolder.mTreeHeaderExpand.setImageResource(nodeBean.getIcon()); } viewHolder.mTreeHeaderCheckBox.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setChecked(nodeBean, viewHolder.mTreeHeaderCheckBox.isChecked()); //Interface call onTreeCheckedChangeListener(nodeBean, position, viewHolder.mTreeHeaderCheckBox.isChecked()); } }); if (nodeBean.isChecked()) { viewHolder.mTreeHeaderCheckBox.setChecked(true); } else { viewHolder.mTreeHeaderCheckBox.setChecked(false); } } @NotNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) { View inflate = View.inflate(mContext, R.layout.tree_header_item, null); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); inflate.setLayoutParams(params); return new ViewHolder(inflate); } static class ViewHolder extends RecyclerView.ViewHolder { private CheckBox mTreeHeaderCheckBox; private TextView mTreeHeaderName; private ImageView mTreeHeaderExpand; public ViewHolder(View itemView) { super(itemView); mTreeHeaderCheckBox = itemView.findViewById(R.id.mTreeHeaderCheckBox); mTreeHeaderName = itemView.findViewById(R.id.mTreeHeaderName); mTreeHeaderExpand = itemView.findViewById(R.id.mTreeHeaderExpand); } } }
4.第四步 新建TreeHelper工具类:
/** * @author CJF */ public class TreeHelper { private volatile static TreeHelper treeHelper = null; public static TreeHelper getInstance() { if (null == treeHelper) { synchronized (TreeHelper.class) { if (null == treeHelper) { treeHelper = new TreeHelper(); } } } return treeHelper; } /** * 传入node 返回排序后的Node * 拿到用户传入的数据,转化为List<Node>以及设置Node间关系,然后根节点,从根往下遍历进行排序; * * @param datas * @param defaultExpandLevel 默认显示 * @return * @throws IllegalArgumentException * @throws IllegalAccessException */ public List<NodeBean> getSortedNodes(List<NodeBean> datas, int defaultExpandLevel) { List<NodeBean> result = new ArrayList<NodeBean>(); // Set the parent-child relationship between Nodes List<NodeBean> nodeBeans = convetData2Node(datas); // get the root node List<NodeBean> rootNodeBeans = getRootNodes(nodeBeans); // Sort and set the relationship between Nodes for (NodeBean nodeBean : rootNodeBeans) { addNode(result, nodeBean, defaultExpandLevel, 1); } return result; } /** * Filter out all visible Nodes * The code for filtering Node is very simple, traverse all Nodes, add and return as long as the root node or parent node is expanded * * @param nodeBeans * @return */ public List<NodeBean> filterVisibleNode(List<NodeBean> nodeBeans) { List<NodeBean> result = new ArrayList<NodeBean>(); for (NodeBean nodeBean : nodeBeans) { // 如果为跟节点,或者上层目录为展开状态 if (nodeBean.isRootNode() || nodeBean.isParentExpand()) { setNodeIcon(nodeBean); result.add(nodeBean); } } return result; } /** * 将我们的数据转化为树的节点 * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系 */ private List<NodeBean> convetData2Node(List<NodeBean> nodeBeans) { for (int i = 0; i < nodeBeans.size(); i++) { NodeBean n = nodeBeans.get(i); for (int j = i + 1; j < nodeBeans.size(); j++) { NodeBean m = nodeBeans.get(j); if (m.getPid() != null) { //n时m的父节点 if (m.getPid().equals(n.getId())) { n.getChildren().add(m); m.setParent(n); //m is the parent node of n } else if (m.getId().equals(n.getPid())) { m.getChildren().add(n); n.setParent(m); } } else { 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 nodeBeans; } /** * Get the root node * * @param nodeBeans * @return */ private List<NodeBean> getRootNodes(List<NodeBean> nodeBeans) { List<NodeBean> root = new ArrayList<NodeBean>(); for (NodeBean nodeBean : nodeBeans) { if (nodeBean.isRootNode()) { root.add(nodeBean); } } return root; } /** * Hang all the content on a node * Through recursion, put all the child nodes on a node in order */ private <T> void addNode(List<NodeBean> nodeBeans, NodeBean<T> nodeBean, int defaultExpandLeval, int currentLevel) { nodeBeans.add(nodeBean); if (defaultExpandLeval >= currentLevel) { nodeBean.setExpand(true); } if (nodeBean.isLeaf()) { return; } for (int i = 0; i < nodeBean.getChildren().size(); i++) { addNode(nodeBeans, nodeBean.getChildren().get(i), defaultExpandLeval, currentLevel + 1); } } /** * Set the icon of the node * * @param nodeBean */ private void setNodeIcon(NodeBean nodeBean) { if (nodeBean.getChildren().size() > 0 && nodeBean.isExpand()) { nodeBean.setIcon(nodeBean.iconExpand); } else if (nodeBean.getChildren().size() > 0 && !nodeBean.isExpand()) { nodeBean.setIcon(nodeBean.iconNoExpand); } else { nodeBean.setIcon(-1); } } }
5. The fifth step is to create a new NodeBean class:
/** * @author CJF */ public class NodeBean<T> { /** * current node id */ private String id; /** * parent node id */ private String pid; /** * Node data entity class */ private T data; /** * Set the picture to turn on and off */ public int iconExpand = -1, iconNoExpand = -1; /** * node name */ private String name; /** * current level */ private int level; /** * Whether to expand */ private boolean isExpand = false; private int icon = -1; /** * Child Node of the next level */ private List<NodeBean> children = new ArrayList<>(); /** * Father Node */ private NodeBean parent; /** * Whether it is checked or not */ private boolean isChecked; public NodeBean() { } public NodeBean(String id, String pid, String name) { this.id = id; this.pid = pid; this.name = name; } public NodeBean(String id, String pid, T data, String name) { this.id = id; this.pid = pid; this.data = data; this.name = name; } /** * Whether it is the root node * * @return */ public boolean isRootNode() { return parent == null; } /** * Determine whether the parent node is expanded * * @return */ public boolean isParentExpand() { if (parent == null) { return false; } return parent.isExpand(); } /** * Whether it is a leaf node * * @return */ public boolean isLeaf() { return children.size() == 0; } /** * Get the current level level */ public int getLevel() { return parent == null ? 0 : parent.getLevel() + 1; } /** * Set to expand * * @param isExpand */ public void setExpand(boolean isExpand) { this.isExpand = isExpand; if (!isExpand) { for (NodeBean nodeBean : children) { nodeBean.setExpand(isExpand); } } } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPid() { return pid; } public void setPid(String pid) { this.pid = pid; } public T getData() { return data; } public void setData(T data) { this.data = data; } public int getIconExpand() { return iconExpand; } public void setIconExpand(int iconExpand) { this.iconExpand = iconExpand; } public int getIconNoExpand() { return iconNoExpand; } public void setIconNoExpand(int iconNoExpand) { this.iconNoExpand = iconNoExpand; } 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 int getIcon() { return icon; } public void setIcon(int icon) { this.icon = icon; } public List<NodeBean> getChildren() { return children; } public void setChildren(List<NodeBean> children) { this.children = children; } public NodeBean getParent() { return parent; } public void setParent(NodeBean parent) { this.parent = parent; } public boolean isChecked() { return isChecked; } public void setChecked(boolean checked) { isChecked = checked; } }
6.第六步 新建TreeHeaderView自定义View类:
/** * @author CJF */ public class TreeHeaderView extends LinearLayout { private TreeRecyclerViewAdapter adapter; private List<NodeBean> dataList = new ArrayList<>(); public TreeHeaderView(Context context) { super(context); initView(context); initData(); } public TreeHeaderView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); initData(); } public TreeHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); initData(); } private void initView(Context context) { //Tree structure layout View headerLayout = LayoutInflater.from(context).inflate(R.layout.tree_header_layout, this, true); // LinearLayoutManager manager = new LinearLayoutManager(context); //Disable sliding layout manager LinearLayoutManager manager = new LinearLayoutManager(context) { //Prohibit vertical sliding of RecyclerView to vertical state (VERTICAL) @Override public boolean canScrollVertically() { return false; } //Prohibit horizontal sliding of RecyclerView to horizontal state (HORIZONTAL) /*@Override public boolean canScrollHorizontally() { return false; }*/ }; RecyclerView mTreeHeaderViewRecy = headerLayout.findViewById(R.id.mTreeHeaderViewRecy); mTreeHeaderViewRecy.setLayoutManager(manager); //The first parameter ListView & RecyclerView // second parameter context //The third parameter data set //The fourth parameter defaults to expand the number of levels 0 to not expand //The icon expanded by the fifth parameter //The sixth parameter closed icon adapter = new TreeRecyclerViewAdapter(mTreeHeaderViewRecy, context, dataList, 0, R.drawable.svg_expand_more, R.drawable.svg_navigate_next); mTreeHeaderViewRecy.setAdapter(adapter); } private void initData() { //root node 0 1 2 dataList.add(new NodeBean<>("0", "-1", "A level 1 node")); dataList.add(new NodeBean<>("1", "-1", "B level 1 node")); dataList.add(new NodeBean<>("2", "-1", "C level 1 node")); // //Second level node of root node 1 dataList.add(new NodeBean<>("3", "0", "A level 2 node")); dataList.add(new NodeBean<>("4", "0", "A 2-level node")); dataList.add(new NodeBean<>("5", "0", "A level 2 node")); //Second level node of root node 2 dataList.add(new NodeBean<>("6", "1", "B level 2 node")); dataList.add(new NodeBean<>("7", "1", "B level 2 node")); dataList.add(new NodeBean<>("8", "1", "B level 2 node")); //Second level node of root node 3 dataList.add(new NodeBean<>("9", "2", "C level 2 node")); dataList.add(new NodeBean<>("10", "2", "C level 2 node")); dataList.add(new NodeBean<>("11", "2", "C level 2 node")); //Third level node dataList.add(new NodeBean<>("12", "3", "A 3rd level node")); dataList.add(new NodeBean<>("13", "3", "A 3rd level node")); dataList.add(new NodeBean<>("14", "3", "A 3rd level node")); dataList.add(new NodeBean<>("15", "4", "A 3rd level node")); dataList.add(new NodeBean<>("16", "4", "A 3rd level node")); dataList.add(new NodeBean<>("17", "4", "A 3rd level node")); dataList.add(new NodeBean<>("18", "5", "A 3rd level node")); dataList.add(new NodeBean<>("19", "5", "A 3rd level node")); dataList.add(new NodeBean<>("20", "5", "A 3rd level node")); //fourth level node dataList.add(new NodeBean<>("21", "12", "A level 4 node")); // fifth level node dataList.add(new NodeBean<>("22", "21", "A level 5 node")); //sixth level node dataList.add(new NodeBean<>("23", "22", "A 6-level node")); // Seventh level node dataList.add(new NodeBean<>("24", "23", "A 7th level node")); //Eight level nodes dataList.add(new NodeBean<>("25", "24", "A 8-level node")); adapter.addData(dataList); // //Get all nodes // final List<Node> allNodes = mAdapter.getAllNodes(); // for (Node allNode : allNodes) { // Log.e("TAG1231", "onCreate: " + allNode.getName()); // } //Selected state monitoring adapter.setTreeCheckedChangeListener(new TreeRecyclerViewAdapter.OnTreeCheckedChangeListener() { @Override public void onCheckChange(NodeBean nodeBean, int position, boolean isChecked) { Log.e("TAG1231", "onCheckChange position: " + nodeBean.getName()); //Get all selected nodes List<NodeBean> selectedNodeBean = adapter.getSelectedNode(); StringBuilder builder = new StringBuilder(); for (NodeBean n : selectedNodeBean) { builder.append(n.getName()).append("\n"); } Log.e("TAG1231", "builder: " + builder); } }); //Total item click status monitoring adapter.setOnTreeClickListener(new BaseTreeRecyclerViewAdapter.OnTreeClickListener() { @Override public void onClick(NodeBean nodeBean, int position) { Log.e("TAG1231", "setOnTreeClickListener: " + nodeBean.getName()); } }); } }
7. The seventh step is to create a new TreeListAdapter adapter class to adapt to ordinary list data:
/** * @author CJF */ public class TreeListAdapter extends BaseQuickAdapter<String, BaseViewHolder> { public TreeListAdapter(int layoutResId) { super(layoutResId); } @Override protected void convert(BaseViewHolder helper, String item) { helper.setText(R.id.mOppoSdkTextPosition, String.valueOf(helper.getAdapterPosition())); helper.setText(R.id.mOppoSdkText, item); } }
8. The eighth step is to create a new TreeListActivity page and put it into the header layout and common data:
/** * @author CJF */ public class TreeListActivity extends AppCompatActivity { private final LinearLayoutManager manager = new LinearLayoutManager(this); private final TreeListAdapter adapter=new TreeListAdapter(R.layout.sdk_item); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tree_list); RecyclerView mTreeListRecy = findViewById(R.id.mTreeListRecy); mTreeListRecy.setLayoutManager(manager); mTreeListRecy.setAdapter(adapter); TreeHeaderView treeHeaderView = new TreeHeaderView(this); adapter.addHeaderView(treeHeaderView); ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { list.add("data"+i); } adapter.addData(list); } }
//manifest don't forget to register activity:
<activity android:name=".phone.activity.TreeListActivity" android:launchMode="singleTop" android:screenOrientation="portrait" android:windowSoftInputMode="stateHidden" tools:ignore="LockedOrientationActivity" />
9. The ninth step is each xml layout file:
//activity_tree_list:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@color/color_white" android:orientation="vertical"> <com.scwang.smartrefresh.layout.SmartRefreshLayout android:layout_width="match_parent" android:layout_height="match_parent" app:srlEnableLoadMore="true" app:srlEnableRefresh="true"> <android.support.v7.widget.RecyclerView android:id="@+id/mTreeListRecy" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.scwang.smartrefresh.layout.footer.FalsifyFooter android:layout_width="match_parent" android:layout_height="@dimen/dp_50" /> <com.scwang.smartrefresh.layout.header.FalsifyHeader android:layout_width="match_parent" android:layout_height="@dimen/dp_50" /> </com.scwang.smartrefresh.layout.SmartRefreshLayout> </LinearLayout>
//sdk_item:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_5" android:layout_marginLeft="@dimen/dp_20" android:layout_marginRight="@dimen/dp_20" android:layout_marginTop="@dimen/dp_5" android:background="@drawable/selector_common_item" android:gravity="center" android:orientation="horizontal"> <TextView android:id="@+id/mOppoSdkTextPosition" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:background="@drawable/selector_common_item" android:gravity="center" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="0" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> <TextView android:id="@+id/mOppoSdkText" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="8" android:background="@drawable/selector_common_item" android:gravity="left|center_vertical" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="text" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> </LinearLayout>
//tree_header_layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_white" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/mTreeHeaderViewRecy" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
//tree_header_item:
?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_5" android:layout_marginLeft="@dimen/dp_20" android:layout_marginRight="@dimen/dp_20" android:layout_marginTop="@dimen/dp_5" android:gravity="center" android:orientation="horizontal"> <ImageView android:src="@drawable/svg_navigate_next" android:id="@+id/mTreeHeaderExpand" android:layout_width="@dimen/dp_50" android:layout_height="@dimen/dp_50" android:gravity="center" android:padding="@dimen/dp_10" /> <CheckBox android:id="@+id/mTreeHeaderCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/mTreeHeaderName" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="left|center_vertical" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="text" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> </LinearLayout>
10. In the tenth step, two svg xml image files:
//svg_expand_more:
<vector android:alpha="0.85" android:autoMirrored="true" android:height="24dp" android:tint="#3B76EC" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#ffffffff" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/> </vector>
//svg_navigate_next:
<vector android:alpha="0.85" android:autoMirrored="true" android:height="24dp" android:tint="#3B76EC" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#ffffffff" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/> </vector>
//2. The second method uses the BaseQuickAdapter library to realize an infinite hierarchical tree structure, and the code is simpler:
1. The first step is to import the dependent library:
//RecyclerView implementation 'com.android.support:recyclerview-v7:28.0.0' //RecyclerAdapter implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28'
2. The second step is to create a new Tree2ListActivity page code as follows:
/** * @author CJF */ public class Tree2ListActivity extends AppCompatActivity { private final LinearLayoutManager manager = new LinearLayoutManager(this); private final TreeListAdapter adapter = new TreeListAdapter(R.layout.sdk_item); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tree_list); RecyclerView mTreeListRecy = findViewById(R.id.mTreeListRecy); mTreeListRecy.setLayoutManager(manager); mTreeListRecy.setAdapter(adapter); Tree2HeaderView tree2HeaderView = new Tree2HeaderView(this); adapter.addHeaderView(tree2HeaderView); ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { list.add("data" + i); } adapter.addData(list); }
//manifest registration:
<activity android:name=".phone.activity.Tree2ListActivity" android:launchMode="singleTop" android:screenOrientation="portrait" android:windowSoftInputMode="stateHidden" tools:ignore="LockedOrientationActivity" />
3. The third step is to create a new TreeListAdapter common data adapter:
/** * @author CJF */ public class TreeListAdapter extends BaseQuickAdapter<String, BaseViewHolder> { public TreeListAdapter(int layoutResId) { super(layoutResId); } @Override protected void convert(BaseViewHolder helper, String item) { helper.setText(R.id.mOppoSdkTextPosition, String.valueOf(helper.getAdapterPosition())); helper.setText(R.id.mOppoSdkText, item); } }
4. The fourth step is to create a new Tree2HeaderView to customize the layout of the view header:
/** * @author CJF */ public class Tree2HeaderView extends LinearLayout implements BaseQuickAdapter.OnItemClickListener { private final Tree2HeaderAdapter adapter=new Tree2HeaderAdapter(R.layout.tree_header_item); private List<NodeBean> mAllNodeBeans; private List<NodeBean> mNodeBeans; public Tree2HeaderView(Context context) { super(context); initView(context); initData(); } public Tree2HeaderView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); initData(); } public Tree2HeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); initData(); } private void initView(Context context) { //树结构布局 View headerLayout = LayoutInflater.from(context).inflate(R.layout.tree_header_layout, this, true); // LinearLayoutManager manager = new LinearLayoutManager(context); //禁止滑动 布局管理器 LinearLayoutManager manager = new LinearLayoutManager(context) { //禁止竖向滑动 RecyclerView 为垂直状态(VERTICAL) @Override public boolean canScrollVertically() { return false; } //禁止横向滑动 RecyclerView 为水平状态(HORIZONTAL) /*@Override public boolean canScrollHorizontally() { return false; }*/ }; RecyclerView mTreeHeaderViewRecy = headerLayout.findViewById(R.id.mTreeHeaderViewRecy); mTreeHeaderViewRecy.setLayoutManager(manager); mTreeHeaderViewRecy.setAdapter(adapter); adapter.setOnItemClickListener(this); } private void initData() { List<NodeBean> maxDataList = new ArrayList<>(); //根节点0 1 2 maxDataList.add(new NodeBean<>("0", "-1", "A 1级节点")); maxDataList.add(new NodeBean<>("1", "-1", "B 1级节点")); maxDataList.add(new NodeBean<>("2", "-1", "C 1级节点")); // //根节点1的二级节点 maxDataList.add(new NodeBean<>("3", "0", "A 2级节点")); maxDataList.add(new NodeBean<>("4", "0", "A 2级节点")); maxDataList.add(new NodeBean<>("5", "0", "A 2级节点")); //根节点2的二级节点 maxDataList.add(new NodeBean<>("6", "1", "B 2级节点")); maxDataList.add(new NodeBean<>("7", "1", "B 2级节点")); maxDataList.add(new NodeBean<>("8", "1", "B 2级节点")); //根节点3的二级节点 maxDataList.add(new NodeBean<>("9", "2", "C 2级节点")); maxDataList.add(new NodeBean<>("10", "2", "C 2级节点")); maxDataList.add(new NodeBean<>("11", "2", "C 2级节点")); //三级节点 maxDataList.add(new NodeBean<>("12", "3", "A 3级节点")); maxDataList.add(new NodeBean<>("13", "3", "A 3rd level node")); maxDataList.add(new NodeBean<>("14", "3", "A 3rd level node")); maxDataList.add(new NodeBean<>("15", "4", "A 3rd level node")); maxDataList.add(new NodeBean<>("16", "4", "A 3rd level node")); maxDataList.add(new NodeBean<>("17", "4", "A 3rd level node")); maxDataList.add(new NodeBean<>("18", "5", "A 3rd level node")); maxDataList.add(new NodeBean<>("19", "5", "A 3rd level node")); maxDataList.add(new NodeBean<>("20", "5", "A 3rd level node")); //fourth level node maxDataList.add(new NodeBean<>("21", "12", "A Level 4 node")); // fifth level node maxDataList.add(new NodeBean<>("22", "21", "A 5th level node")); //sixth level node maxDataList.add(new NodeBean<>("23", "22", "A 6-level node")); // Seventh level node maxDataList.add(new NodeBean<>("24", "23", "A 7-level node")); //Eight level nodes maxDataList.add(new NodeBean<>("25", "24", "A 8-level node")); maxDataList.add(new NodeBean<>("26", "25", "A 9th level node")); maxDataList.add(new NodeBean<>("27", "26", "A 10-level node")); maxDataList.add(new NodeBean<>("28", "27", "A 11th level node")); maxDataList.add(new NodeBean<>("29", "28", "A 12-level node")); maxDataList.add(new NodeBean<>("30", "29", "A 13-level node")); maxDataList.add(new NodeBean<>("31", "30", "A 14-level node")); maxDataList.add(new NodeBean<>("32", "31", "A 15-level node")); maxDataList.add(new NodeBean<>("33", "32", "A 16-level node")); maxDataList.add(new NodeBean<>("34", "33", "A 17-level node")); maxDataList.add(new NodeBean<>("35", "-1", "D level 1 node")); maxDataList.add(new NodeBean<>("36", "-1", "E level 1 node")); maxDataList.add(new NodeBean<>("37", "-1", "F level 1 node")); maxDataList.add(new NodeBean<>("38", "35", "D level 2 node")); maxDataList.add(new NodeBean<>("39", "36", "E level 2 node")); maxDataList.add(new NodeBean<>("40", "37", "F level 2 node")); /** * Sort all Nodes */ mAllNodeBeans = TreeHelper.getInstance().getSortedNodes(maxDataList, 0); /** * Filter out visible Nodes */ mNodeBeans = TreeHelper.getInstance().filterVisibleNode(mAllNodeBeans); adapter.addData(mNodeBeans); // //Get all nodes // final List<Node> allNodes = mAdapter.getAllNodes(); // for (Node allNode : allNodes) { // Log.e("TAG1231", "onCreate: " + allNode.getName()); // } } @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { expandOrCollapse(adapter,position); } /** * The click event of the corresponding ListView expands or closes a node * * @param position */ public void expandOrCollapse(BaseQuickAdapter adapter,int position) { NodeBean n = (NodeBean) adapter.getData().get(position); // Exclude the incoming parameter error exception if (n != null) { if (!n.isLeaf()) { n.setExpand(!n.isExpand()); mNodeBeans = TreeHelper.getInstance().filterVisibleNode(mAllNodeBeans); //replace the view adapter.replaceData(mNodeBeans); } } } }
5. The fifth step is to create a new Tree2HeaderAdapter adapter to adapt to the tree structure data:
public class Tree2HeaderAdapter extends BaseQuickAdapter<NodeBean, BaseViewHolder> { public Tree2HeaderAdapter(int layoutResId) { super(layoutResId); } @Override protected void convert(BaseViewHolder helper, NodeBean item) { //最大展开8级宽度距离 int level = Math.min(item.getLevel(), 8); // 设置内边距 helper.itemView.setPadding(level * 50, 10, 10, 10); //设置文字内容 helper.setText(R.id.mTreeHeaderName, item.getName()); //设置展开与收起的图片 helper.setImageResource(R.id.mTreeHeaderExpand, item.isExpand() ? R.drawable.svg_expand_more : R.drawable.svg_navigate_next); //图片显示隐藏 helper.setVisible(R.id.mTreeHeaderExpand, item.getChildren().size() > 0); } }
6.第六步 新建TreeHelper工具类:
/** * @author CJF */ public class TreeHelper { private volatile static TreeHelper treeHelper = null; public static TreeHelper getInstance() { if (null == treeHelper) { synchronized (TreeHelper.class) { if (null == treeHelper) { treeHelper = new TreeHelper(); } } } return treeHelper; } /** * Pass in node and return sorted Node * Get the data passed in by the user, convert it into a List<Node> and set the relationship between Nodes, and then the root node, traverse from the root down for sorting; * * @param dates * @param defaultExpandLevel display by default * @return * @throws IllegalArgumentException * @throws IllegalAccessException */ public List<NodeBean> getSortedNodes(List<NodeBean> datas, int defaultExpandLevel) { List<NodeBean> result = new ArrayList<NodeBean>(); // Set the parent-child relationship between Nodes List<NodeBean> nodeBeans = convetData2Node(datas); // get the root node List<NodeBean> rootNodeBeans = getRootNodes(nodeBeans); // Sort and set the relationship between Nodes for (NodeBean nodeBean : rootNodeBeans) { addNode(result, nodeBean, defaultExpandLevel, 1); } return result; } /** * Filter out all visible Nodes * The code for filtering Node is very simple, traverse all Nodes, add and return as long as the root node or parent node is expanded * * @param nodeBeans * @return */ public List<NodeBean> filterVisibleNode(List<NodeBean> nodeBeans) { List<NodeBean> result = new ArrayList<NodeBean>(); for (NodeBean nodeBean : nodeBeans) { // If it is a root node, or the upper directory is expanded if (nodeBean.isRootNode() || nodeBean.isParentExpand()) { setNodeIcon(nodeBean); result.add(nodeBean); } } return result; } /** * Convert our data into tree nodes * Set the parent-child relationship between Nodes; compare every two nodes to set the relationship */ public List<NodeBean> convetData2Node(List<NodeBean> nodeBeans) { for (int i = 0; i < nodeBeans.size(); i++) { NodeBean n = nodeBeans.get(i); for (int j = i + 1; j < nodeBeans.size(); j++) { NodeBean m = nodeBeans.get(j); if (m.getPid() != null) { //n is the parent node of m if (m.getPid().equals(n.getId())) { n.getChildren().add(m); m.setParent(n); //m is the parent node of n } else if (m.getId().equals(n.getPid())) { m.getChildren().add(n); n.setParent(m); } } else { 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 nodeBeans; } /** * Get the root node * * @param nodeBeans * @return */ public List<NodeBean> getRootNodes(List<NodeBean> nodeBeans) { List<NodeBean> root = new ArrayList<NodeBean>(); for (NodeBean nodeBean : nodeBeans) { if (nodeBean.isRootNode()) { root.add(nodeBean); } } return root; } /** * Hang all the content on a node * Through recursion, put all the child nodes on a node in order */ public <T> void addNode(List<NodeBean> nodeBeans, NodeBean<T> nodeBean, int defaultExpandLeval, int currentLevel) { nodeBeans.add(nodeBean); if (defaultExpandLeval >= currentLevel) { nodeBean.setExpand(true); } if (nodeBean.isLeaf()) { return; } for (int i = 0; i < nodeBean.getChildren().size(); i++) { addNode(nodeBeans, nodeBean.getChildren().get(i), defaultExpandLeval, currentLevel + 1); } } /** * Set the icon of the node * * @param nodeBean */ private void setNodeIcon(NodeBean nodeBean) { if (nodeBean.getChildren().size() > 0 && nodeBean.isExpand()) { nodeBean.setIcon(nodeBean.iconExpand); } else if (nodeBean.getChildren().size() > 0 && !nodeBean.isExpand()) { nodeBean.setIcon(nodeBean.iconNoExpand); } else { nodeBean.setIcon(-1); } } }
7. The seventh step is to create a new NodeBean class:
/** * @author CJF */ public class NodeBean<T> { /** * current node id */ private String id; /** * parent node id */ private String pid; /** * Node data entity class */ private T data; /** * Set the picture to turn on and off */ public int iconExpand = -1, iconNoExpand = -1; /** * node name */ private String name; /** * current level */ private int level; /** * Whether to expand */ private boolean isExpand = false; private int icon = -1; /** * Child Node of the next level */ private List<NodeBean> children = new ArrayList<>(); /** * Father Node */ private NodeBean parent; /** * Whether it is checked or not */ private boolean isChecked; public NodeBean() { } public NodeBean(String id, String pid, String name) { this.id = id; this.pid = pid; this.name = name; } public NodeBean(String id, String pid, T data, String name) { this.id = id; this.pid = pid; this.data = data; this.name = name; } /** * Whether it is the root node * * @return */ public boolean isRootNode() { return parent == null; } /** * Determine whether the parent node is expanded * * @return */ public boolean isParentExpand() { if (parent == null) { return false; } return parent.isExpand(); } /** * Whether it is a leaf node * * @return */ public boolean isLeaf() { return children.size() == 0; } /** * Get the current level level */ public int getLevel() { return parent == null ? 0 : parent.getLevel() + 1; } /** * Set to expand * * @param isExpand */ public void setExpand(boolean isExpand) { this.isExpand = isExpand; if (!isExpand) { for (NodeBean nodeBean : children) { nodeBean.setExpand(isExpand); } } } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPid() { return pid; } public void setPid(String pid) { this.pid = pid; } public T getData() { return data; } public void setData(T data) { this.data = data; } public int getIconExpand() { return iconExpand; } public void setIconExpand(int iconExpand) { this.iconExpand = iconExpand; } public int getIconNoExpand() { return iconNoExpand; } public void setIconNoExpand(int iconNoExpand) { this.iconNoExpand = iconNoExpand; } 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 int getIcon() { return icon; } public void setIcon(int icon) { this.icon = icon; } public List<NodeBean> getChildren() { return children; } public void setChildren(List<NodeBean> children) { this.children = children; } public NodeBean getParent() { return parent; } public void setParent(NodeBean parent) { this.parent = parent; } public boolean isChecked() { return isChecked; } public void setChecked(boolean checked) { isChecked = checked; } }
8. The eighth step is to create a new xml layout file that needs to be used:
//activity_tree_list:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:background="@color/color_white" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/mTreeListRecy" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
//sdk_item:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_5" android:layout_marginLeft="@dimen/dp_20" android:layout_marginRight="@dimen/dp_20" android:layout_marginTop="@dimen/dp_5" android:gravity="center" android:orientation="horizontal"> <TextView android:id="@+id/mOppoSdkTextPosition" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:gravity="center" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="0" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> <TextView android:id="@+id/mOppoSdkText" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="8" android:gravity="left|center_vertical" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="text" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> </LinearLayout>
//tree_header_layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/color_white" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/mTreeHeaderViewRecy" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
//tree_header_item:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/dp_5" android:layout_marginLeft="@dimen/dp_20" android:layout_marginRight="@dimen/dp_20" android:layout_marginTop="@dimen/dp_5" android:orientation="vertical"> <LinearLayout android:id="@+id/mTreeHeaderLin" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal"> <ImageView android:id="@+id/mTreeHeaderExpand" android:layout_width="@dimen/dp_50" android:layout_height="@dimen/dp_50" android:gravity="center" android:padding="@dimen/dp_10" android:src="@drawable/svg_navigate_next" /> <TextView android:id="@+id/mTreeHeaderName" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="left|center_vertical" android:maxLines="1" android:minHeight="@dimen/dp_50" android:padding="@dimen/dp_10" android:text="text" android:textColor="@color/black" android:textSize="@dimen/sp_15" /> </LinearLayout> </LinearLayout>
9. In the ninth step, two svg xml image files:
//svg_expand_more:
<vector android:alpha="0.85" android:autoMirrored="true" android:height="24dp" android:tint="#3B76EC" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#ffffffff" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/> </vector>
//svg_navigate_next:
<vector android:alpha="0.85" android:autoMirrored="true" android:height="24dp" android:tint="#3B76EC" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillColor="#ffffffff" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/> </vector>
//---------------------------------------------------------------END----------------------------------------------------------