unity自制多级树型菜单,实现分层次列表展开,仿unity编辑界面Hierarchy窗口

       想实现一个菜单分级的功能,专业术语叫做树型菜单,很多应用软件的目录有采用该方式,层次感很强,结构清晰,但UGUI并没有原生的插件,去网上搜了一下,发现相关资源并不多,调试出一大堆Bug,于是自己决定按照unity界面Hierarchy样式做一个,目录结构清晰,可动态添加,删除,修改子菜单,理论上可无限级设置子菜单。

模板效果如下:

思路

首先发现每个级的ItemPanel(一个单级菜单条)样式是一样的,于是需要一个panel来装所有的itemPanel,

(ItemPanel)

在整个菜单顶部panel上添加vertically layout group组件,使下面的子菜单从上到下依次排列,暂时不用管里面内容的排列,往里添加itemPanel就行,这样就会形成一级菜单目录。

然后在一级菜单下面添加二级菜单,将要添加的二级菜单变成一级菜单的子物体,二级菜单所使用的ItemPanel原则上和一级菜单一样,只不过他变成了一级菜单的子物体。三级四级菜单依次类推,也就是其上一级的子物体。

(效果图)(编辑结构图)

 

实现过程:

主要有两个类,一个ItemBeanBase,一个ItemPanelBase,其中ItemBeanBase是一个属性类的基类,可根据自己在单个菜单中的需要填充的数据组装成一个属性类。然后就是ItemPanelBase,他是单个菜单ItemPanel的基类,需要每个子菜单ItemPanel都挂载其子类,可根据自己的需要定制继承ItemPanelBase类,该类用于处理每个ItemPanel与父菜单及子菜单的逻辑关系。因此可以根据自己的需要定制ItemPanel的UI样式以及其中内容。

给出ItemPanelBase类和ItemPanel类;

ItemPanel类(继承至ItemPanelBase),重写里面的虚方法InitPanelContent(),实现数据(ItemBean)的定制,挂在每个子菜单ItemPanel上面。

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

public class ItemPanelBase : MonoBehaviour
{
    private List<ItemPanelBase> childList;//子物体集合
    [HideInInspector]
    public Button downArrow;//下箭头按钮
    public Sprite down, right,dot;
    public bool isOpen { get; set; }//子物体开启状态
    private Vector2 startSize;//起始大小

    private void Awake()
    {
        childList = new List<ItemPanelBase>();
        downArrow = this.transform.Find("ContentPanel/ArrowButton").GetComponent<Button>();
        downArrow.onClick.AddListener(() =>
        {
            if (isOpen)
            {
                CloseChild();
                isOpen = false;
            }
            else
            {
                OpenChild();
                isOpen = true;
            }
        });
        startSize = this.GetComponent<RectTransform>().sizeDelta;
        isOpen = false;
    }

    //添加子物体到集合
    private void AddChild(ItemPanelBase parentItemPanelBase)
    {
        childList.Add(parentItemPanelBase);
        if (childList.Count >= 1)
        {
            downArrow.GetComponent<Image>().sprite = right;
        }
    }

    /// <summary>
    /// 设置父物体,父物体不为一级菜单
    /// </summary>
    /// <param name="parentItemPanelBase"></param>
    public void SetItemParent(ItemPanelBase parentItemPanelBase)
    {
        this.transform.parent = parentItemPanelBase.transform;
        parentItemPanelBase.AddChild(this);
        this.GetComponent<VerticalLayoutGroup>().padding = new RectOffset((int)parentItemPanelBase.downArrow.GetComponent<RectTransform>().sizeDelta.x, 0, 0, 0);
        if (parentItemPanelBase.isOpen)
        {
            
            this.GetComponent<ItemPanelBase>().AddParentSize((int)this.gameObject.GetComponent<RectTransform>().sizeDelta.y);
        }
        else
        {
            this.transform.gameObject.SetActive(false);        
        }
    }

    /// <summary>
    /// 设置父物体,父物体为一级菜单
    /// </summary>
    /// <param name="tran"></param>
    public void SetBaseParent(Transform tran)
    {
        this.transform.parent = tran;
    }

    /// <summary>
    /// 增加一个子物体后更新Panel大小
    /// </summary>
    /// <param name="change"></param>
    public void UpdateRectTranSize(int change)
    {
        this.gameObject.GetComponent<RectTransform>().sizeDelta = new Vector2(startSize.x, this.gameObject.GetComponent<RectTransform>().sizeDelta.y + change);
    }
    /// <summary>
    /// 增加父物体高度
    /// </summary>
    /// <param name="parentItem"></param>
    /// <param name="change"></param>
    public void AddParentSize(int change)
    {
        if (this.transform.parent.GetComponent<ItemPanelBase>() != null)
        {
            this.transform.parent.GetComponent<ItemPanelBase>().UpdateRectTranSize(change);
            this.transform.parent.GetComponent<ItemPanelBase>().AddParentSize(change);
        }
    }

    /// <summary>
    /// 关闭子物体列表
    /// </summary>
    public void CloseChild()
    {
        if (childList.Count == 0) return;
        foreach (ItemPanelBase child in childList)
        {
            child.gameObject.SetActive(false);
            child.GetComponent<ItemPanelBase>().AddParentSize(-(int)child.gameObject.GetComponent<RectTransform>().sizeDelta.y);
        }
        downArrow.GetComponent<Image>().sprite = right;
    }

    /// <summary>
    /// 打开子物体列表
    /// </summary>
    public void OpenChild()
    {
        if (childList.Count == 0) return;
        foreach (ItemPanelBase child in childList)
        {
            child.gameObject.SetActive(true);
            child.GetComponent<ItemPanelBase>().AddParentSize((int)child.gameObject.GetComponent<RectTransform>().sizeDelta.y);
        }
        downArrow.GetComponent<Image>().sprite = down;
    }

    //填充Item数据
    public virtual void InitPanelContent(ItemBeanbase itemBeanbase) { }

}
public class ItemPanel : ItemPanelBase
{

    public override void InitPanelContent(ItemBeanbase itemBeanbase)
    {
        base.InitPanelContent(itemBeanbase);
        ItemBean itemBean = (ItemBean)itemBeanbase;
        this.transform.Find("ContentPanel/Text").GetComponent<Text>().text = itemBean.name + itemBean.age;
    }
}

下面是树型菜单的调用代码:

public class PullDownList : MonoBehaviour
{
    private List<GameObject> itemPanelList;
    public GameObject itemPanel;


    private void Awake()
    {
        itemPanelList = new List<GameObject>();
    }
    // Use this for initialization
    void Start()
    {
        for (int i = 0; i < 10; i++)
        {
            GameObject newItemPanel = Instantiate(itemPanel);
            itemPanelList.Add(newItemPanel);
            newItemPanel.GetComponent<ItemPanelBase>().SetBaseParent(this.transform);
            newItemPanel.GetComponent<ItemPanelBase>().InitPanelContent(new ItemBean("一级菜单" + i, i));
        }

        for (int i = 0; i < 5; i++)
        {
            GameObject newItemPanel2 = Instantiate(itemPanel);
            itemPanelList.Add(newItemPanel2);
            newItemPanel2.GetComponent<ItemPanelBase>().SetItemParent(itemPanelList[i].GetComponent<ItemPanelBase>());
            newItemPanel2.GetComponent<ItemPanelBase>().InitPanelContent(new ItemBean("二级菜单" + i, i));
        }

        for (int i = 0; i < 2; i++)
        {
            GameObject newItemPanel3 = Instantiate(itemPanel);
            itemPanelList.Add(newItemPanel3);
            newItemPanel3.GetComponent<ItemPanelBase>().SetItemParent(itemPanelList[11].GetComponent<ItemPanelBase>());
            newItemPanel3.GetComponent<ItemPanelBase>().InitPanelContent(new ItemBean("三级菜单" + i, i));
        }
    }

    private void Update()
    {
        if (Input.GetKeyUp(KeyCode.S))
        {
            for (int i = 0; i < 2; i++)
            {
                GameObject newItemPanel4 = Instantiate(itemPanel);
                itemPanelList.Add(newItemPanel4);
                newItemPanel4.GetComponent<ItemPanelBase>().SetItemParent(itemPanelList[1].GetComponent<ItemPanelBase>());
                newItemPanel4.GetComponent<ItemPanelBase>().InitPanelContent(new ItemBean("二级菜单" + i, i));
            }
        }
    }
}

需要itemPanel的预制体来生成每个子菜单,然后建立一个list保存所有的子菜单项,方便后续操作,也可动态根据添加,删除,修改菜单项,因为采用了vertical layout group组件,所以只需要修改里面的子物体即可实现菜单视图动态更新,不需要刷新操作。

整个菜单初始效果图如下:

左侧为实际效果,右侧为其在unity中的结构目录

上面由于方便看清菜单间层次,没有取消ItemPanel的背景颜色,下面给出对比图

(itemPanel背景颜色存在,方便看清结构)

不同的项目对UI有不同需求,因此这里只是实现了树型菜单的逻辑结构,可根据自己的需要定制UI即内容,可根据需要自己修改ItemPanel预制体的样式即可。

操作GIF图, 很丝滑。。。

整个项目内容比较简单,结构清晰,核心代码只有100多行,可扩展性较强,可根据自己需要对功能做修改,因此不明白的地方可以直接看源码,下面给出unitypackage(2017.3.1导出)的百度云盘链接,由于水平有限,难免有地方出错或者不规范,望多指点。

地址:https://pan.baidu.com/s/1sCHhK2m3xpYoFTUc4Uiksg

提取码:iac9

 

 

  • 20
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值