Unity TreeView 树形菜单

news2025/1/22 8:50:21

文章目录

  • 1. 参考文章
  • 2. 工程地址
  • 3. 项目结构
  • 4. 主要代码

1. 参考文章

https://blog.csdn.net/qq992817263/article/details/54925472

2. 工程地址

将文件夹放入 unity 中即可查看
作者 github 地址:https://github.com/ccUnity3d/TreeView
本人 gitee 地址(不用翻墙):https://gitee.com/hwm-ming/unity-tree-view

3. 项目结构

在这里插入图片描述

4. 主要代码

TreeViewControl(控制类)

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
/// <summary>
/// 树形菜单控制器
/// </summary>
public class TreeViewControl : MonoBehaviour
{
    /// <summary>
    /// 当前树形菜单的数据源
    /// </summary>
    [HideInInspector]
    public List<TreeViewData> Data = null;
    /// <summary>
    /// 树形菜单中元素的模板
    /// </summary>
    public GameObject Template;
    /// <summary>
    /// 树形菜单中元素的根物体
    /// </summary>
    public Transform TreeItems;
    /// <summary>
    /// 树形菜单的纵向排列间距
    /// </summary>
    public int VerticalItemSpace = 2;
    /// <summary>
    /// 树形菜单的横向排列间距
    /// </summary>
    public int HorizontalItemSpace = 25;
    /// <summary>
    /// 树形菜单中元素的宽度
    /// </summary>
    public int ItemWidth = 230;
    /// <summary>
    /// 树形菜单中元素的高度
    /// </summary>
    public int ItemHeight = 35;
    /// <summary>
    /// 所有子元素的鼠标点击回调事件
    /// </summary>
    public delegate void ClickItemdelegate(GameObject item);
    public event ClickItemdelegate ClickItemEvent;

    //当前树形菜单中的所有元素
    private List<GameObject> _treeViewItems;
    //当前树形菜单中的所有元素克隆体(刷新树形菜单时用于过滤计算)
    private List<GameObject> _treeViewItemsClone;
    //树形菜单当前刷新队列的元素位置索引
    private int _yIndex = 0;
    //树形菜单当前刷新队列的元素最大层级
    private int _hierarchy = 0;
    //正在进行刷新
    private bool _isRefreshing = false;

    void Awake()
    {
        ClickItemEvent += ClickItemTemplate;
    }
    /// <summary>
    /// 鼠标点击子元素事件
    /// </summary>
    public void ClickItem(GameObject item)
    {
        ClickItemEvent(item);
    }
    void ClickItemTemplate(GameObject item)
    {
        //空的事件,不这样做的话ClickItemEvent会引发空引用异常
    }

    /// <summary>
    /// 返回指定名称的子元素是否被勾选
    /// </summary>
    public bool ItemIsCheck(string itemName)
    {
        for (int i = 0; i < _treeViewItems.Count; i++)
        {
            if (_treeViewItems[i].transform.Find("TreeViewText").GetComponent<Text>().text == itemName)
            {
                return _treeViewItems[i].transform.Find("TreeViewToggle").GetComponent<Toggle>().isOn;
            }
        }
        return false;
    }
    /// <summary>
    /// 返回树形菜单中被勾选的所有子元素名称
    /// </summary>
    public List<string> ItemsIsCheck()
    {
        List<string> items = new List<string>();

        for (int i = 0; i < _treeViewItems.Count; i++)
        {
            if (_treeViewItems[i].transform.Find("TreeViewToggle").GetComponent<Toggle>().isOn)
            {
                items.Add(_treeViewItems[i].transform.Find("TreeViewText").GetComponent<Text>().text);
            }
        }

        return items;
    }

    /// <summary>
    /// 生成树形菜单
    /// </summary>
    public void GenerateTreeView()
    {
        //删除可能已经存在的树形菜单元素
        if (_treeViewItems != null)
        {
            for (int i = 0; i < _treeViewItems.Count; i++)
            {
                Destroy(_treeViewItems[i]);
            }
            _treeViewItems.Clear();
        }
        //重新创建树形菜单元素
        _treeViewItems = new List<GameObject>();
        for (int i = 0; i < Data.Count; i++)
        {
            GameObject item = Instantiate(Template);

            if (Data[i].ParentID == -1)
            {
                item.GetComponent<TreeViewItem>().SetHierarchy(0);
                item.GetComponent<TreeViewItem>().SetParent(null);
            }
            else
            {
                TreeViewItem tvi = _treeViewItems[Data[i].ParentID].GetComponent<TreeViewItem>();
                item.GetComponent<TreeViewItem>().SetHierarchy(tvi.GetHierarchy() + 1);
                item.GetComponent<TreeViewItem>().SetParent(tvi);
                tvi.AddChildren(item.GetComponent<TreeViewItem>());
            }

            item.transform.name = "TreeViewItem";
            item.transform.Find("TreeViewText").GetComponent<Text>().text = Data[i].Name;
            item.transform.SetParent(TreeItems);
            item.transform.localPosition = Vector3.zero;
            item.transform.localScale = Vector3.one;
            item.transform.localRotation = Quaternion.Euler(Vector3.zero);
            item.SetActive(true);

            _treeViewItems.Add(item);
        }

        foreach (var item in _treeViewItems)
        {
            if (item.GetComponent<TreeViewItem>().GetChildrenNumber() == 0)
            {
                item.transform.Find("ContextButton").gameObject.SetActive(false);
            }
        }
    }

    /// <summary>
    /// 刷新树形菜单
    /// </summary>
    public void RefreshTreeView()
    {
        //上一轮刷新还未结束
        if (_isRefreshing)
        {
            return;
        }

        _isRefreshing = true;
        _yIndex = 0;
        _hierarchy = 0;

        //复制一份菜单
        _treeViewItemsClone = new List<GameObject>(_treeViewItems);

        //用复制的菜单进行刷新计算
        for (int i = 0; i < _treeViewItemsClone.Count; i++)
        {
            //已经计算过或者不需要计算位置的元素
            if (_treeViewItemsClone[i] == null || !_treeViewItemsClone[i].activeSelf)
            {
                continue;
            }

            TreeViewItem tvi = _treeViewItemsClone[i].GetComponent<TreeViewItem>();

            _treeViewItemsClone[i].GetComponent<RectTransform>().localPosition = new Vector3(tvi.GetHierarchy() * HorizontalItemSpace, _yIndex,0);
            _yIndex += (-(ItemHeight + VerticalItemSpace));
            if (tvi.GetHierarchy() > _hierarchy)
            {
                _hierarchy = tvi.GetHierarchy();
            }

            //如果子元素是展开的,继续向下刷新
            if (tvi.IsExpanding)
            {
                RefreshTreeViewChild(tvi);
            }

            _treeViewItemsClone[i] = null;
        }

        //重新计算滚动视野的区域
        float x = _hierarchy * HorizontalItemSpace + ItemWidth;
        float y = Mathf.Abs(_yIndex);
        transform.GetComponent<ScrollRect>().content.sizeDelta = new Vector2(x, y);

        //清空复制的菜单
        _treeViewItemsClone.Clear();

        _isRefreshing = false;
    }
    /// <summary>
    /// 刷新元素的所有子元素
    /// </summary>
    void RefreshTreeViewChild(TreeViewItem tvi)
    {
        for (int i = 0; i < tvi.GetChildrenNumber(); i++)
        {
            tvi.GetChildrenByIndex(i).gameObject.GetComponent<RectTransform>().localPosition = new Vector3(tvi.GetChildrenByIndex(i).GetHierarchy() * HorizontalItemSpace, _yIndex, 0);
            _yIndex += (-(ItemHeight + VerticalItemSpace));
            if (tvi.GetChildrenByIndex(i).GetHierarchy() > _hierarchy)
            {
                _hierarchy = tvi.GetChildrenByIndex(i).GetHierarchy();
            }

            //如果子元素是展开的,继续向下刷新
            if (tvi.GetChildrenByIndex(i).IsExpanding)
            {
                RefreshTreeViewChild(tvi.GetChildrenByIndex(i));
            }

            int index = _treeViewItemsClone.IndexOf(tvi.GetChildrenByIndex(i).gameObject);
            if (index >= 0)
            {
                _treeViewItemsClone[index] = null;
            }
        }
    }
}

TreeViewItem(子项)

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
/// <summary>
/// 树形菜单元素
/// </summary>
public class TreeViewItem : MonoBehaviour
{
    /// <summary>
    /// 树形菜单控制器
    /// </summary>
    public TreeViewControl Controler;
    /// <summary>
    /// 当前元素的子元素是否展开(展开时可见)
    /// </summary>
    public bool IsExpanding = false;

    //当前元素在树形图中所属的层级
    private int _hierarchy = 0;
    //当前元素指向的父元素
    private TreeViewItem _parent;
    //当前元素的所有子元素
    private List<TreeViewItem> _children;
    //正在进行刷新
    private bool _isRefreshing = false;

    void Awake()
    {
        // // 没有子元素隐藏下拉按钮
        // if (GetChildrenNumber() == 0)
        // {
        //     transform.Find("ContextButton").gameObject.SetActive(false);
        // }
        //上下文按钮点击回调
        transform.Find("ContextButton").GetComponent<Button>().onClick.AddListener(ContextButtonClick);
        transform.Find("TreeViewButton").GetComponent<Button>().onClick.AddListener(delegate () {
            Controler.ClickItem(gameObject);
        });
    }
    /// <summary>
    /// 点击上下文菜单按钮,元素的子元素改变显示状态
    /// </summary>
    void ContextButtonClick()
    {
        //上一轮刷新还未结束
        if (_isRefreshing)
        {
            return;
        }

        _isRefreshing = true;

        if (IsExpanding)
        {
            transform.Find("ContextButton").GetComponent<RectTransform>().localRotation = Quaternion.Euler(0, 0, 90);
            IsExpanding = false;
            ChangeChildren(this, false);
        }
        else
        {
            transform.Find("ContextButton").GetComponent<RectTransform>().localRotation = Quaternion.Euler(0, 0, 0);
            IsExpanding = true;
            ChangeChildren(this, true);
        }

        //刷新树形菜单
        Controler.RefreshTreeView();

        _isRefreshing = false;
    }
    /// <summary>
    /// 改变某一元素所有子元素的显示状态
    /// </summary>
    void ChangeChildren(TreeViewItem tvi, bool value)
    {
        for (int i = 0; i < tvi.GetChildrenNumber(); i++)
        {
            tvi.GetChildrenByIndex(i).gameObject.SetActive(value);
            if (tvi.GetChildrenByIndex(i).IsExpanding)
            {
                ChangeChildren(tvi.GetChildrenByIndex(i), value);
            }
        }
    }

    #region 属性访问
    public int GetHierarchy()
    {
        return _hierarchy;
    }
    public void SetHierarchy(int hierarchy)
    {
        _hierarchy = hierarchy;
    }
    public TreeViewItem GetParent()
    {
        return _parent;
    }
    public void SetParent(TreeViewItem parent)
    {
        _parent = parent;
    }
    public void AddChildren(TreeViewItem children)
    {
        if (_children == null)
        {
            _children = new List<TreeViewItem>();
        }
        _children.Add(children);
    }
    public void RemoveChildren(TreeViewItem children)
    {
        if (_children == null)
        {
            return;
        }
        _children.Remove(children);
    }
    public void RemoveChildren(int index)
    {
        if (_children == null || index < 0 || index >= _children.Count)
        {
            return;
        }
        _children.RemoveAt(index);
    }
    public int GetChildrenNumber()
    {
        if (_children == null)
        {
            return 0;
        }
        return _children.Count;
    }
    public TreeViewItem GetChildrenByIndex(int index)
    {
        if (index >= _children.Count)
        {
            return null;
        }
        return _children[index];
    }
    #endregion
}

TreeViewData(数据类)

/// <summary>
/// 树形菜单数据
/// </summary>
public class TreeViewData
{
    /// <summary>
    /// 数据内容
    /// </summary>
    public string Name;
    /// <summary>
    /// 数据所属的父ID
    /// </summary>
    public int ParentID;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/883643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IronPDF for .NET Crack

IronPDF for .NET Crack ronPDF现在将等待HTML元素加载后再进行渲染。 IronPDF现在将等待字体加载后再进行渲染。 添加了在绘制文本时指定旋转的功能。 添加了在保存为PDFA时指定自定义颜色配置文件的功能。 IronPDF for.NET允许开发人员在C#、F#和VB.NET for.NET Core和.NET F…

【数据库系统】-- 【1】DBMS概述

1.DBMS概述 01数据库系统概述02数据库技术发展概述03关系数据库概述04数据库基准测试 01数据库系统概述 几个基本概念 为什么使用数据库系统 数据库发展的辉煌历程 02数据库技术发展概述 数据模型 应用领域 ● OLTP ● OLAP ● HTAP ● GIS OLTP与OLAP 与其他技术相…

基于C#的消息处理的应用程序 - 开源研究系列文章

今天讲讲基于C#里的基于消息处理的应用程序的一个例子。 我们知道&#xff0c;Windows操作系统的程序是基于消息处理的。也就是说&#xff0c;程序接收到消息代码定义&#xff0c;然后根据消息代码定义去处理对应的操作。前面有一个博文例子( C#程序的启动显示方案(无窗口进程发…

actuator/prometheus使用pushgateway上传jvm监控数据

场景 准备 prometheus已经部署pushgateway服务&#xff0c;访问{pushgateway.server:9091}可以看到面板 实现 基于springboot引入支持组件&#xff0c;版本可以 <!--监控检查--><dependency><groupId>org.springframework.boot</groupId><artifa…

【刷题笔记8.15】【链表相关】LeetCode:合并两个有序链表、反转链表

LeetCode&#xff1a;【链表相关】合并两个有序链表 题目1&#xff1a;合并两个有序链表 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3…

第14集丨Vue2 基础教程 —— 生命周期

目录 一、引子1.1 实现一1.2 一个死循环的写法1.3 mounted实现 二、生命周期2.1 概念2.2 常用的生命周期钩子2.3 关于销毁Vue实例注意点2.4 vm的一生(vm的生命周期)2.5 生命周期图示 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如&#xff0c;需要设置数据监听、…

如何能够写出带货的爆文?

网络推广这个领域&#xff0c;公司众多价格差别很大&#xff0c;就拿软文文案这块来讲&#xff0c;有人报价几十块&#xff0c;也有人报价几千块。作为企业的营销负责人往往会被价格吸引&#xff0c;比价择优选用&#xff0c;结果写出来的文案不满意&#xff0c;修改也无从入手…

vs2019 vs2022默认以管理员身份运行

找到快捷方式属性&#xff0c;点高级&#xff0c;把“用管理员身份运行”打勾再确定&#xff0c;之前是有个兼容性选项卡的&#xff0c;在没有选项卡的情况下就用这种方法

【【STM32-USART串口协议】】

STM32-USART串口协议 USART串口协议 •通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 •通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 就是我们并不能在芯片上设计完全部的一下子完成所有的设计&…

C++ 之动态链接库DLL使用注意事项及C#调用详解

C 之动态链接库DLL使用注意事项及C#调用详解 有时候算法开发完成之后需要封装成动态链接库DLL来进行集成&#xff0c;一方面增加了算法or代码的复用或者广泛使用性&#xff0c;另一方面也起了保密的效果平时封装成DLL之后放到一台新的电脑上会出现问题&#xff0c;所以本文总结…

企事业数字培训及知识库平台

前言 随着信息化的进一步推进&#xff0c;目前各行各业都在进行数字化转型&#xff0c;本人从事过医疗、政务等系统的研发&#xff0c;和客户深入交流过日常办公中“知识”的重要性&#xff0c;再加上现在倡导的互联互通、数据安全、无纸化办公等概念&#xff0c;所以无论是企业…

Gitlab-第四天-CD到k8s集群的坑

一、.gitlab-ci.yml #CD到k8s集群的 stages: - deploy-test build-image-deploy-test: stage: deploy-test image: bitnami/kubectl:latest # 使用一个包含 kubectl 工具的镜像 tags: - k8s script: - ls -al - kubectl apply -f deployment.yaml # 根据实际情况替换…

UDS (Unified Diagnostic Services)汽车诊断标准协议

作者博客主页 作者 : Eterlove 一笔一画&#xff0c;记录我的学习生活&#xff01;站在巨人的肩上Standing on Shoulders of Giants! 该文章为原创&#xff0c;转载请注明出处和作者 参考文献&#xff1a; 《道路车辆统一诊断服务(UDS) Road vehicles - Unified diagnostic s…

【Windows 常用工具系列 8 -- 修改鼠标光标(指针)大小和颜色的快速方法方法】

文章目录 修改方法 上篇文章&#xff1a;Windows 常用工具系列 7 – 禁用win10自带的微软输入法 修改方法 Win键 i 快捷键进入设置页面&#xff0c;然后输入光标... 就会跳出修改鼠标大小与光标颜色的选项。 Win键是在计算机键盘左下角Ctrl和Alt键之间的按键 根据自己的需求…

强训第32

选择 D B A A 发送TCP意思应该是已经建立了连接&#xff0c;会超时重传。在未建立连接的时候&#xff0c;会放弃该链接 C A 80端口是http A 交换机攻击主要有五种&#xff1a;VLAN跳跃攻击 生成树攻击 MAC表洪水攻击 ARP攻击 VTP攻击 B A 2^(32-26)2^(32-27)2^(32-27)128 减去…

6.3 社会工程学攻击

数据参考&#xff1a;CISP官方 目录 社会工程学攻击概念社会工程学攻击利用的人性 “弱点”典型社会工程学攻击方式社会工程学攻击防护 一、社会工程学攻击概念 什么是社会工程学攻击 也被称为 "社交工程学" 攻击利用人性弱点 (本能反应、贪婪、易于信任等) 进…

你永远想象不到有多折磨的 Android 开发 react-native gradle*!¥%#

很难过&#xff0c;拿到项目运行不起来&#xff0c;错误报告研究几天没研究明白&#xff0c;改代码&#xff0c;装gradle&#xff0c;忙和好久还是一个样&#xff0c;也不知道是码的问题还是什么&#xff0c;一开始 后面装完gradle&#xff0c;不报错了&#xff0c;但是也跑不起…

leetcode 力扣刷题哈希表初尝试

哈希表 刷题初尝试 哈希表基础知识242. 有效的字母异位词383. 赎金信49. 字母异位词分组438. 找到字符串中所有字母异位词 哈希表基础知识 哈希表是一种数据结构&#xff0c;也叫散列表。哈希表中存储的是键值对&#xff0c;即(key&#xff0c;value)&#xff0c;根据key直接查…

draw.io导出矢量图到word报错text is not svg - cannot display

先参考https://blog.csdn.net/a625750076/article/details/126384831 如果不行&#xff0c;可能是转存的问题 解决方法&#xff1a;直接在draw.io上操作 第一步 第二步 然后再word中粘贴&#xff0c;依旧是矢量图哦&#xff01;

LeetCode150道面试经典题-- 汇总区间(简单)

1.题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,…