unity 目录树

5 篇文章 0 订阅

最近项目上需要对一些编组信息进行树状展示,为了通用,将目录树写成一个组件,完整的代码包括测试代码已经上传到了github上目录树代码,代码需要配合RectTransform的锚点使用,所以写了一个编辑器扩展,可以像创建其他UI组件一样创建一个目录树编辑器代码

使用方式

1.在编辑器中创建一个Tree

在这里插入图片描述

2.在代码中获取并创建

  首先要在对应面板中获取到Tree组件,用于构造一棵树的函数为

Tree.GenerateTree(TreeNode rootNode)

  这个函数需要传入一个节点,这个节点可以是一个空节点,也可以是一个已经构造好父子关系的根节点。传入之后,会根据这个节点的子节点信息创建一棵树。
  TreeNode支持链式添加父子关系,可以像下面一样构造一棵树。

TreeNode root = new TreeNode("1");

root.AddChild(new TreeNode("11")
        .AddChild(new TreeNode("111")
            .AddChild(new TreeNode("1111")
                .AddChild(new TreeNode("11111"))
                .AddChild(new TreeNode("11112")))
            .AddChild(new TreeNode("1112")))
        .AddChild(new TreeNode("112")))
    .AddChild(new TreeNode("12")
        .AddChild(new TreeNode("121"))
        .AddChild(new TreeNode("122")
            .AddChild(new TreeNode("1221"))
            .AddChild(new TreeNode("1222")))
        .AddChild(new TreeNode("123")))
    .AddChild(new TreeNode("13")
        .AddChild(new TreeNode("131")));

tree.GenerateTree(root);

  但大多数情况下,树的信息应该是由另外一个信息类或者数据表之类的东西转来的,所以父子关系的创建需要自己写。

代码

1.树的节点

  在大多数的时候外界并不需要对TreeNode进行直接控制,但是在初始化树的时候,要传入一个已经设置好父子关系的根节点,这个类里面的几个核心方法都利用了递归,所以在更新一个节点的信息的时候,这个节点的所有子节点也会改变。
树的节点的初始化是根据对应的模板来,目前还不是很灵活,模板结构变化比较大的话需要修改InitEnity方法。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace XFramework.UI
{
    /// <summary>
    /// 树节点
    /// </summary>
    public class TreeNode
    {
        private RectTransform m_Rect;
        /// <summary>
        /// 当前结点所归属的树
        /// </summary>
        private Tree m_Tree;
        /// <summary>
        /// 子结点
        /// </summary>
        private List<TreeNode> m_Childs;
        /// <summary>
        /// 父结点
        /// </summary>
        private TreeNode m_Parent;
        /// <summary>
        /// 开启状态
        /// </summary>
        private bool m_IsOn;
        /// <summary>
        /// 层级
        /// </summary>
        private int m_level;

        /// <summary>
        /// 显示文字
        /// </summary>
        public string Text { get; private set; }

        public TreeNode()
        {
            m_IsOn = true;
            m_Childs = new List<TreeNode>();
        }

        public TreeNode(string text) : this()
        {
            this.Text = text;
        }

        /// <summary>
        /// 初始化场景中对应的实体
        /// </summary>
        /// <param name="tree"></param>
        private void InitEnity(Tree tree)
        {
            m_Tree = tree;

            // 创建自己
            if (m_Parent == null)
            {
                m_Rect = Object.Instantiate(m_Tree.NodeTemplate, m_Tree.NodeTemplate.transform.parent.Find("Root")).GetComponent<RectTransform>();
                m_level = 0;
            }
            else
            {
                m_Rect = Object.Instantiate(m_Tree.NodeTemplate, m_Parent.m_Rect.Find("Child")).GetComponent<RectTransform>();
                m_level = m_Parent.m_level + 1;
            }

            // UI组件设置
            m_Rect.Find("Toggle").GetComponent<Toggle>().onValueChanged.AddListener((value) =>
            {
                m_IsOn = value;

                Refresh(value);
                Root.RefreshPos();

                tree.onOn_Off.Invoke(value, this);
            });

            m_Rect.Find("Button").GetComponent<Button>().onClick.AddListener(() =>
            {
                tree.onSelectNode.Invoke(this);
            });

            m_Rect.Find("Button").Find("Text").GetComponent<Text>().text = this.Text;
        }

        /// <summary>
        /// 刷新位置及显示隐藏
        /// </summary>
        private void Refresh(bool isOn)
        {
            isOn &= m_IsOn;
            if (isOn)
            {
                foreach (var item in m_Childs)
                {
                    item.Refresh(isOn);
                    item.m_Rect.localScale = new Vector3(1, 1, 1);
                }
            }
            else
            {
                foreach (var item in m_Childs)
                {
                    item.Refresh(isOn);
                    item.m_Rect.localScale = new Vector3(1, 0, 1);
                }
            }
        }

        /// <summary>
        /// 刷新位置
        /// </summary>
        public void RefreshPos()
        {
            int index = 0;

            if (m_Parent != null)
            {
                foreach (var item in m_Parent.m_Childs)
                {
                    if (item == this)
                        break;
                    index += item.GetItemCount();
                }
            }

            m_Rect.anchoredPosition = new Vector2(0, -index * 30);

            foreach (var item in m_Childs)
            {
                item.RefreshPos();
            }
        }

        /// <summary>
        /// 添加一个父子关系
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public TreeNode AddChild(TreeNode item)
        {
            m_Childs.Add(item);
            item.m_Parent = this;
            return this;
        }

        public TreeNode AddChild(string text)
        {
            return AddChild(new TreeNode(text));
        }

        /// <summary>
        /// 根据已有的父子关系创建一颗(子)树
        /// </summary>
        /// <param name="m_Parent"></param>
        /// <param name="gameObject"></param>
        public void CreateTree(Tree tree)
        {
            InitEnity(tree);

            // 继续创建
            foreach (var child in m_Childs)
            {
                child.CreateTree(m_Tree);
            }

            if (m_Parent == null)
            {
                RefreshPos();
            }
        }

        /// <summary>
        /// 添加一个父子关系并创建实体
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public TreeNode CreateChild(TreeNode item)
        {
            AddChild(item);

            item.InitEnity(m_Tree);
            Refresh(m_IsOn);
            Root.RefreshPos();
            return this;
        }

        public TreeNode CreateChild(string text)
        {
            TreeNode item = new TreeNode(text);
            return CreateChild(item);
        }

        /// <summary>
        /// 删除自身
        /// </summary>
        public void Delete()
        {
            if(m_Parent == null)
            {
                Debug.Log("根结点不能删除");
                return;
            }
            Object.Destroy(m_Rect.gameObject);
            m_Parent.m_Childs.Remove(this);
            Root.RefreshPos();
        }

        /// <summary>
        /// 子物体的数量 +1
        /// </summary>
        public int GetItemCount()
        {
            if (m_Childs.Count == 0 || !m_IsOn)
            {
                return 1;
            }
            else
            {
                int count = 0;
                foreach (var item in m_Childs)
                {
                    count += item.GetItemCount();
                }
                return count + 1;
            }
        }

        /// <summary>
        /// 获取自己在父物体种的索引
        /// </summary>
        public int GetSiblingIndex()
        {
            if(m_Parent != null)
            {
                int index = 0;
                foreach (var item in m_Parent.m_Childs)
                {
                    if (item == this)
                        return index;
                }
            }
            return 0;
        }

        /// <summary>
        /// 根结点
        /// </summary>
        public TreeNode Root
        {
            get
            {
                TreeNode item = this;
                while(item.m_Parent != null)
                {
                    item = item.m_Parent;
                }
                return item;
            }
        }

        /// <summary>
        /// 重置父物体
        /// </summary>
        /// <param name="parent"></param>
        public void SetParent(TreeNode parent)
        {
            m_Parent.m_Childs.Remove(this);
            parent.AddChild(this);
        }

        /// <summary>
        /// 通过字符串找寻子节点
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public TreeNode Find(string path)
        {
            var temp = path.Split(new char[] { '/' }, 2);
            if (temp.Length == 1)
                return this;

            TreeNode node = null;
            foreach (var item in m_Childs)
            {
                if (item.Text == temp[0])
                {
                    node = item;
                    break;
                }
            }

            if (node == null)
                return node;
            else
                return node.Find(temp[1]);
        }

        /// <summary>
        /// 根据索引获取子节点
        /// </summary>
        public TreeNode GetChild(int index)
        {
            return m_Childs[index];
        }
    }
}

2.树

  这个类的使用方式就像Button一样,可以先注册事件,然后调用GenerateTree在场景中构造一棵树,注意事件的实际调用者不是Tree而是TreeNode

using UnityEngine;

namespace XFramework.UI
{
    /// <summary>
    /// 目录树
    /// </summary>
    public class Tree : MonoBehaviour
    {
        /// <summary>
        /// 模板
        /// </summary>
        public GameObject NodeTemplate { get; private set; }
        /// <summary>
        /// 树的根节点
        /// </summary>
        private TreeNode m_RootTreeNode;

        public string rootText = "Root";

        /// <summary>
        /// 节点被选中的事件
        /// </summary>
        public TreeEvent onSelectNode = new TreeEvent();
        /// <summary>
        /// 节点展开关闭事件
        /// </summary>
        public SwitchEvent onOn_Off = new SwitchEvent();

        private void Awake()
        {
            NodeTemplate = transform.Find("NodeTemplate").gameObject;
            NodeTemplate.GetComponent<RectTransform>().anchoredPosition = new Vector2(10000, 10000);
        }

        /// <summary>
        /// 构造一棵树
        /// </summary>
        /// <param name="rootNode">父子关系已经设置好的根节点</param>
        public void GenerateTree(TreeNode rootNode)
        {
            if (m_RootTreeNode != null)
                m_RootTreeNode.Delete();

            m_RootTreeNode = rootNode;

            m_RootTreeNode.CreateTree(this);
        }

        /// <summary>
        /// 删除某个节点
        /// </summary>
        /// <param name="path">路径</param>
        public bool Delete(string path)
        {
            TreeNode node = m_RootTreeNode.Find(path);
            if (node != null)
            {
                node.Delete();
                return true;
            }
            return false;
        }

        public class TreeEvent : UnityEngine.Events.UnityEvent<TreeNode> { }
        public class SwitchEvent : UnityEngine.Events.UnityEvent<bool,TreeNode> { }
    }
}

3.Editor

  最后放上编辑器代码,可以直接在场景中创建出一棵树

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;

/// <summary>
/// 用于创建一些自定义UI组件
/// </summary>
public class CreateComponent
{
    private static DefaultControls.Resources s_StandardResources;

    private const string kUILayerName = "UI";

    private const string kStandardSpritePath = "UI/Skin/UISprite.psd";
    private const string kBackgroundSpritePath = "UI/Skin/Background.psd";
    private const string kInputFieldBackgroundPath = "UI/Skin/InputFieldBackground.psd";
    private const string kKnobPath = "UI/Skin/Knob.psd";
    private const string kCheckmarkPath = "UI/Skin/Checkmark.psd";
    private const string kDropdownArrowPath = "UI/Skin/DropdownArrow.psd";
    private const string kMaskPath = "UI/Skin/UIMask.psd";

    [MenuItem("GameObject/UI/Tree")]
    public static void CreateTree()
    {
        GameObject parent = Selection.activeGameObject;

        RectTransform tree = new GameObject("Tree").AddComponent<RectTransform>();
        tree.SetParent(parent.transform);
        tree.localPosition = Vector3.zero;
        tree.gameObject.AddComponent<XFramework.UI.Tree>();
        tree.sizeDelta = new Vector2(180, 30);

        // 设置模板
        RectTransform itemTemplate = new GameObject("NodeTemplate").AddComponent<RectTransform>();
        itemTemplate.SetParent(tree);
        itemTemplate.pivot = new Vector2(0, 1);
        itemTemplate.anchorMin = new Vector2(0.5f, 1);
        itemTemplate.anchorMax = new Vector2(0.5f, 1);
        itemTemplate.anchoredPosition = new Vector2(-90, 0);
        itemTemplate.sizeDelta = new Vector2(180, 30);

        RectTransform button = DefaultControls.CreateButton(GetStandardResources()).GetComponent<RectTransform>();
        button.SetParent(itemTemplate);
        button.anchoredPosition = new Vector2(10, 0);
        button.sizeDelta = new Vector2(160, 30);

        RectTransform toggle = DefaultControls.CreateToggle(GetStandardResources()).GetComponent<RectTransform>();
        toggle.SetParent(itemTemplate);
        Object.DestroyImmediate(toggle.Find("Label").gameObject);
        toggle.anchoredPosition = new Vector2(-80, 0);
        toggle.sizeDelta = new Vector2(20, 20);

        RectTransform child = new GameObject("Child").AddComponent<RectTransform>();
        child.SetParent(itemTemplate);
        child.pivot = new Vector2(0, 1);
        child.anchorMin = new Vector2(0, 1);
        child.anchorMax = new Vector2(0, 1);
        child.sizeDelta = Vector2.zero;
        child.anchoredPosition = new Vector2(20, -30);


        // 设置树的跟结点位置
        RectTransform treeRoot = new GameObject("Root").AddComponent<RectTransform>();
        treeRoot.SetParent(tree);
        treeRoot.anchoredPosition = new Vector2(-90, 0);
        treeRoot.sizeDelta = new Vector2(100, 30);
    }

    private static DefaultControls.Resources GetStandardResources()
    {
        if (s_StandardResources.standard == null)
        {
            s_StandardResources.standard = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
            s_StandardResources.background = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpritePath);
            s_StandardResources.inputField = AssetDatabase.GetBuiltinExtraResource<Sprite>(kInputFieldBackgroundPath);
            s_StandardResources.knob = AssetDatabase.GetBuiltinExtraResource<Sprite>(kKnobPath);
            s_StandardResources.checkmark = AssetDatabase.GetBuiltinExtraResource<Sprite>(kCheckmarkPath);
            s_StandardResources.dropdown = AssetDatabase.GetBuiltinExtraResource<Sprite>(kDropdownArrowPath);
            s_StandardResources.mask = AssetDatabase.GetBuiltinExtraResource<Sprite>(kMaskPath);
        }
        return s_StandardResources;
    }
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值