IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 利用RecyclerView.Adapter实现树形菜单 -> 正文阅读

[移动开发]Android 利用RecyclerView.Adapter实现树形菜单

树形菜单在日常开发中经常用到,刚好当前业务有需求,于是自己倒腾了一个工具类,以实现树形菜单,灵感以及原理主要借鉴于: https://www.cnblogs.com/wader2011/archive/2011/11/30/2268877.html
感谢博主分享

实现原理基本一致,
引用原博主原文
“用两个树形节点类集合分别去存储所有节点(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 = 1; 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);
                    TreeData.add(mElement);
                }
HierarchyMenuAdapter TreeListAdapter = new HierarchyMenuAdapter(this, TreeData);

最后

mRecyclerView.setAdapter(mAdapter);

完事!一个树形RecyclerView就完成啦。

哈哈哈哈,因为是后续整理的,就没有截图可供参考了,可以参考原博主的图;实现的效果大致是一样,再次感谢原博主分享;
在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-13 12:13:24  更:2021-08-13 12:15:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 22:40:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码