树形菜单在日常开发中经常用到,刚好当前业务有需求,于是自己倒腾了一个工具类,以实现树形菜单,灵感以及原理主要借鉴于: ,
感谢博主分享
实现原理基本一致,
引用原博主原文
“用两个树形节点类集合分别去存储所有节点(List treeElements)和当前显示节点(List currentElements),当前显示节点集合currentElements中的数据显示在ListView中。当点击含有子节点的节点时(如上图中的A、B、C、CC11)会把相应的子节点从所有节点集合treeElements中找到并添加当前显示节点集合currentElements中在ListView上显示或从currentElements中删除并从ListView刷新数据。”
在此原博主框架下做了一些优化:
1:RecyclerView代替ListView,结点显示/隐藏由于RecyclerView特性效果更佳
2:丰富了节点实体数据内容,各位也可以对其适配于自己的业务需求
实现工具类代码
1:MenuElement
public class MenuElement<T> implements Cloneable {
public int level = -1;// 当前界面层级,用于显示第一级菜单,以及根据等级对节点进行缩进
public String id = null;// 当前节点id
public String code = null;// 当前节点code
public String title = null;// 当前节点文字
public boolean hasParent = false;// 当前节点是否有父节点
public String parentId = null;// 父节点id
public boolean hasChild = false;// 当前节点是否有子节点
public boolean childShow = false;// 如果有子节点,子节点当前是否已显示,是否处于展开状态
public boolean hasCheck = true;// 是否可选
public boolean isCheck = false;// 是否处于选中状态
public String other = null;// 当前节点其他信息
public T TAG = null;//储存绑定实体数据
public T getTAG() {
return TAG;
}
public void setTAG(T TAG) {
this.TAG = TAG;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isHasParent() {
return hasParent;
}
public void setHasParent(boolean hasParent) {
this.hasParent = hasParent;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public boolean isHasChild() {
return hasChild;
}
public void setHasChild(boolean hasChild) {
this.hasChild = hasChild;
}
public boolean isChildShow() {
return childShow;
}
public void setChildShow(boolean childShow) {
this.childShow = childShow;
}
public boolean isHasCheck() {
return hasCheck;
}
public void setHasCheck(boolean hasCheck) {
this.hasCheck = hasCheck;
}
public boolean isCheck() {
return isCheck;
}
public void setCheck(boolean check) {
isCheck = check;
}
public String getOther() {
return other;
}
public void setOther(String other) {
this.other = other;
}
@Override
public MenuElement clone() {
MenuElement stu = null;
try {
stu = (MenuElement) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
2:HierarchyMenuAdapter
import android.content.Context;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import kbdct.wbb.utillib.R;
public class HierarchyMenuAdapter extends RecyclerView.Adapter {
private Context mContext;
List<MenuElement> AllElements = null;// 所有节点集合
List<MenuElement> currentElements = null;// 当前显示的节点集合
List<MenuElement> tempElements = null;// 用于临时存储
List<MenuElement> treeElementsToDel = null; // 临时存储待删除的节点
//是否单选
Boolean isRadio = true;//默认单选模式
//是否选中时同时选中子级
Boolean isReactInChain = false;//默认不关联选中
//是否点击父级时展开子级并反馈点击
Boolean isClickInChain = false;//默认点击父级时只展开子级
public Boolean getRadio() {
return isRadio;
}
/**
* 是否单选;
*
* @param radio
*/
public void setRadio(Boolean radio) {
isRadio = radio;
}
public Boolean getReactInChain() {
return isReactInChain;
}
/**
* 设置是否选中时同时选中子级
* 注意:设置关联选中时,默认取消单选;
*
* @param reactInChain
*/
public void setReactInChain(Boolean reactInChain) {
if (reactInChain) {//设置关联选中时,默认取消单选;
isRadio = false;
}
isReactInChain = reactInChain;
}
public void setClickInChain(Boolean clickInChain) {
isClickInChain = clickInChain;
}
public HierarchyMenuAdapter(Context context) {
this.mContext = context;
}
public void setAllElements(List<MenuElement> allElements) {
AllElements = allElements;
getFirstLevelElements();
}
public List<MenuElement> getAllElements() {
return AllElements;
}
public HierarchyMenuAdapter(Context context, List<MenuElement> baseList) {
this.mContext = context;
this.AllElements = baseList;
getFirstLevelElements();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_menu, parent, false);
return new HierarchyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
HierarchyViewHolder viewHolder = (HierarchyViewHolder) holder;
MenuElement ME = currentElements.get(position);
if (ME.isHasCheck()) {//判断结点是否有单选按钮
viewHolder.cBox.setVisibility(View.VISIBLE);
viewHolder.cBox.setOnCheckedChangeListener(null);
viewHolder.cBox.setChecked(ME.isCheck);
viewHolder.cBox.setTag(R.id.position, position);
viewHolder.cBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton arg0,
boolean arg1) {
if (!arg1 && isRadio) {
notifyDataSetChanged();
return;
}
int position = (int) arg0.getTag(R.id.position);
MenuElement ME = currentElements.get(position);
//如果是单选则把其他选中状态清除
if (isRadio) {
for (int i = 0; i < AllElements.size(); i++) {
AllElements.get(i).setCheck(false);
}
for (int i = 0; i < currentElements.size(); i++) {
currentElements.get(i).setCheck(false);
}
} else {
if (isReactInChain) {//将子级
checkAllChildElementsByParentId(ME.getId(), arg1);
}
}
currentElements.get(position).setCheck(arg1);
// TODO Auto-generated method stub
if (itemCheckClickListener != null) {
itemCheckClickListener.ItemCheckClickListener(arg1, ME);
}
notifyDataSetChanged();
}
});
viewHolder.arrow.setVisibility(View.GONE);
viewHolder.cBox.setVisibility(View.VISIBLE);
} else {
viewHolder.cBox.setVisibility(View.GONE);
viewHolder.arrow.setVisibility(View.VISIBLE);
}
viewHolder.item_Layout.setTag(R.id.position, position);
if (ME.isHasChild()) {// 有子节点,要显示图标
if (ME.isChildShow()) {// 是否打开状态
viewHolder.icon.setImageResource(R.drawable.ic_menu_close);
} else {
viewHolder.icon.setImageResource(R.drawable.ic_menu_open);
}
viewHolder.arrow.setVisibility(View.GONE);
viewHolder.icon.setVisibility(View.VISIBLE);
viewHolder.item_Layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = (int) view.getTag(R.id.position);
MenuElement ME = currentElements.get(position);
int clunm = 0;
if (!ME.isChildShow()) {// 当前父节点展开状态
List<MenuElement> addME = getChildElementsFromAllById(ME.getLevel(), ME.getId());
currentElements.addAll(position + 1, addME);
clunm = addME.size();
Log.e("notifyItemRangeChanged", position + 1 + ">>" + (position + 1 + clunm));
notifyItemRangeInserted(position + 1, clunm);
Log.e("notifyItemRangeChanged", position + clunm + 1 + ">>" + ((position + 1 + clunm) + currentElements.size() - (position + clunm + 1)));
notifyItemRangeChanged(position + clunm + 1, currentElements.size() - (position + clunm + 1));
} else {
int Size1 = currentElements.size();
delAllChildElementsByParentId(ME.getId());
int Size2 = currentElements.size();
clunm = Size1 - Size2;
notifyItemRangeRemoved(position + 1, clunm);
Log.e("notifyItemRangeChanged", position + 1 + ">>" + ((position + 1) + currentElements.size() - (position + 1)));
notifyItemRangeChanged(position + 1, currentElements.size() - (position + 1));
}
currentElements.get(position).setChildShow(!ME.isChildShow());
notifyItemRangeChanged(position, 1);
if (isClickInChain && itemClickListener != null)
itemClickListener.onItemClickListener(ME);
}
});
} else {
// 没有子节点,要隐藏展开图标
viewHolder.icon.setVisibility(View.INVISIBLE);
viewHolder.item_Layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = (int) view.getTag(R.id.position);
MenuElement ME = currentElements.get(position);
if (itemClickListener != null)
itemClickListener.onItemClickListener(ME);
}
});
}
viewHolder.title.setText(ME.getTitle());
viewHolder.item_Layout.setPadding(dpToPx(12) * (ME.getLevel() - 1), 0, 0, 0);// 根据层级设置缩进
// viewHolder.title.setTextSize(14-ME.level); // 根据层级设置字体大小
}
@Override
public int getItemCount() {
if (currentElements != null)
return currentElements.size();
else
return 0;
}
/**
* 初始化树形结构列表数据,把第一层级的数据添加到currentElements中
*/
public void getFirstLevelElements() {
int size = AllElements.size();
currentElements = new ArrayList<MenuElement>();
for (int i = 0; i < size; i++) {
if (AllElements.get(i).getLevel() == 1) {
currentElements.add(AllElements.get(i));
}
}
notifyDataSetChanged();
}
/**
* 从所有节点集合中获取某父节点的子节点集合
*
* @param parentId
* @return
*/
private List<MenuElement> getChildElementsFromAllById(int Level, String parentId) {
tempElements = new ArrayList<MenuElement>();
int size = AllElements.size();
for (int i = 0; i < size; i++) {
if (AllElements.get(i).getParentId() != null && AllElements.get(i).getParentId().equalsIgnoreCase(parentId)) {
if (AllElements.get(i).getLevel() == -1)
AllElements.get(i).setLevel(Level + 1);
tempElements.add(AllElements.get(i));
}
}
return tempElements;
}
/**
* 删除某父节点的所有子节点集合
*
* @param parentId
* @return
*/
private synchronized boolean delAllChildElementsByParentId(String parentId) {
int size;
MenuElement tempElement = currentElements.get(getElementIndexById(parentId));
List<MenuElement> childElments = getChildElementsFromCurrentById(parentId);
if (treeElementsToDel == null) {
treeElementsToDel = new ArrayList<MenuElement>();
} else {
treeElementsToDel.clear();
}
size = childElments.size();
for (int i = 0; i < size; i++) {
tempElement = childElments.get(i);
if (tempElement.hasChild && tempElement.isChildShow()) {
treeElementsToDel.add(tempElement);
}
}
size = treeElementsToDel.size();
for (int i = size - 1; i >= 0; i--) {
delAllChildElementsByParentId(treeElementsToDel.get(i).getId());
}
delDirectChildElementsByParentId(parentId);
return true;
}
/**
* 删除某父节点的直接子节点集合
*
* @param parentId
* @return
*/
private synchronized boolean delDirectChildElementsByParentId(
String parentId) {
boolean success = false;
if (currentElements == null || currentElements.size() == 0) {
return success;
}
synchronized (currentElements) {
int size = currentElements.size();
for (int i = size - 1; i >= 0; i--) {
if (currentElements.get(i).getParentId() != null && currentElements.get(i).getParentId()
.equalsIgnoreCase(parentId)) {
currentElements.get(i).setChildShow(false);// 记得隐藏子节点时把展开状态设为false
currentElements.remove(i);
}
}
}
success = true;
return success;
}
/**
* 修改某父节点的所有子节点集合选中状态
*
* @param parentId
* @return
*/
private synchronized boolean checkAllChildElementsByParentId(String parentId, Boolean isCheck) {
int size;
size = AllElements.size();
for (int i = 0; i < size; i++) {//轮询所有
if (AllElements.get(i).getParentId().equalsIgnoreCase(parentId) && AllElements.get(i).isCheck != isCheck) {
AllElements.get(i).setCheck(isCheck);
if (AllElements.get(i).isHasChild())
checkAllChildElementsByParentId(AllElements.get(i).getId(), isCheck);
}
}
size = currentElements.size();
for (int i = 0; i < size; i++) {//轮询展开
if (currentElements.get(i).getParentId().equalsIgnoreCase(parentId) && currentElements.get(i).isCheck != isCheck) {
currentElements.get(i).setCheck(isCheck);
if (currentElements.get(i).isHasChild())
checkAllChildElementsByParentId(currentElements.get(i).getId(), isCheck);
}
}
return true;
}
/**
* 从当前显示的节点集合中获取某父节点的子节点集合
*
* @param parentId
* @return
*/
private List<MenuElement> getChildElementsFromCurrentById(String parentId) {
if (tempElements == null) {
tempElements = new ArrayList<MenuElement>();
} else {
tempElements.clear();
}
int size = currentElements.size();
for (int i = 0; i < size; i++) {
if (currentElements.get(i).getParentId() != null && currentElements.get(i).getParentId().equalsIgnoreCase(parentId)) {
tempElements.add(currentElements.get(i));
}
}
return tempElements;
}
/**
* 根据id查下标
*
* @param id
* @return
*/
private int getElementIndexById(String id) {
int num = currentElements.size();
for (int i = 0; i < num; i++) {
if (currentElements.get(i).getId().equalsIgnoreCase(id)) {
return i;
}
}
return -1;
}
/**
* 设置某个项选中状态
*/
public void setCheckElements() {
for (int i = 0; i < currentElements.size(); i++) {
if (isRadio)
currentElements.get(i).setCheck(false);
}
notifyDataSetChanged();
}
/**
* 初始化树形结构列表数据,把第一层级的数据添加到currentElements中
*/
public ArrayList<MenuElement> getCheckElements() {
int size = AllElements.size();
ArrayList<MenuElement> CheckElements = new ArrayList<MenuElement>();
for (int i = 0; i < size; i++) {
if (AllElements.get(i).isCheck()) {
CheckElements.add(AllElements.get(i));
}
}
return CheckElements;
}
private class HierarchyViewHolder extends RecyclerView.ViewHolder {
LinearLayout item_Layout;
CheckBox cBox;
ImageView icon;
ImageView arrow;
TextView title;
public HierarchyViewHolder(@NonNull View itemView) {
super(itemView);
item_Layout = (LinearLayout) itemView.findViewById(R.id.item_Layout);
icon = (ImageView) itemView.findViewById(R.id.item_icon);
arrow = (ImageView) itemView.findViewById(R.id.item_arrow);
title = (TextView) itemView.findViewById(R.id.item_title);
cBox = (CheckBox) itemView.findViewById(R.id.item_checkBox);
}
}
ItemClickListener itemClickListener = null;// 用户点击事件回调
ItemCheckClickListener itemCheckClickListener = null;// 用户点击事件回调
/**
* 设置点击事件回调接口
*/
public void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
/**
* 自定义内部接口,用于用户点击最终节点时的事件回调
*/
public interface ItemClickListener {
public void onItemClickListener(MenuElement mMenuElement);
}
/**
* 设置选中事件回调接口
*
* @param
*/
public void setItemCheckClickListener(ItemCheckClickListener itemCheckClickListener) {
this.itemCheckClickListener = itemCheckClickListener;
}
/**
* 自定义内部接口,用于用户点击最终节点时的事件回调
*/
public interface ItemCheckClickListener {
public void ItemCheckClickListener(Boolean isCheck, MenuElement position);
}
private int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics());
}
}
item_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/item_Layout"
android:layout_width="match_parent"
android:layout_height="46dp"
android:background="@drawable/btn_img_br"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/item_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_menu_open" />
<TextView
android:id="@+id/item_title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_marginRight="5dp"
android:layout_weight="1.0"
android:gravity="left|center_vertical"
android:paddingLeft="8dp"
android:text="菜单一"
android:textColor="@color/text_default_color"
android:textSize="14sp" />
<CheckBox
android:id="@+id/item_checkBox"
android:layout_width="36dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:button="@null"
android:drawableLeft="@drawable/checkbox_stye1"
android:focusable="false"
android:padding="6dp" />
<ImageView
android:id="@+id/item_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_chevron_right" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/alpha_2" />
</LinearLayout>
</LinearLayout>
实现方式:
ArrayList<MenuElement> TreeData = new ArrayList<MenuElement>();
for (int i = 0; i < 5; i++) {
MenuElement mElement = new MenuElement();
mElement.setId(i + "");
mElement.setCode("code" + i);
mElement.setTitle("层级" + (i + 1));
mElement.setParentId(i - 1 + "");
mElement.setLevel(i+1);
mElement.setHasChild((i+1)<5);
TreeData.add(mElement);
}
HierarchyMenuAdapter TreeListAdapter = new HierarchyMenuAdapter(this, TreeData);
最后
mRecyclerView.setAdapter(mAdapter);
完事!一个树形RecyclerView就完成啦。
哈哈哈哈,因为是后续整理的,就没有截图可供参考了,可以参考原博主的图;实现的效果大致是一样,再次感谢原博主分享;