Unity TreeView tree menu

1. Reference articles

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

2. Project address

Put the folder into unity to view
the author's github address: https://github.com/ccUnity3d/TreeView
My gitee address (no need to jump over the wall): https://gitee.com/hwm-ming/unity-tree- view

3. Project structure

Insert image description here

4. Main code

TreeViewControl (control class)

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(child)

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(data class)

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

Guess you like

Origin blog.csdn.net/weixin_45136016/article/details/132309917