unity 目錄樹

最近項目上需要對一些編組信息進行樹狀展示,爲了通用,將目錄樹寫成一個組件,完整的代碼包括測試代碼已經上傳到了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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章