Unity实用小工具或脚本——可折叠伸缩的多级列表二(带搜索功能)

Posted 凯尔八阿哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity实用小工具或脚本——可折叠伸缩的多级列表二(带搜索功能)相关的知识,希望对你有一定的参考价值。

一、前言

      继上篇可折叠伸缩的多级(至少三级)内容列表实现了类似于Unity的Unity的Hierarchy视图中的折叠效果之后,我寻思的丰富一下该工具的功能,不保证说完全的复制Unity的Hierarchy视图中的所有功能,但至少其核心的部分,如图1所示,这样的搜索功

图1

能还是让我很心动的,在搜索框中输入关键字就能立马改变Hierarchy项目的显示情况,清空所有的显示并且只显示搜索的结果,而且搜素的结果不包含任何的层级关系,点击搜素后的某一项可以选中它,然后关闭搜索框后边的“X”按钮关闭搜素并且定位到选中的这个项目中,如果之前这个项目在搜索前没有展开,那么在搜素选中后即可展开定位到这项中,无论它是在第几层。看看我实现的效果吧,如图2所示:在这个功能实现中,我没有弄的像Unity那样复杂,在搜素时我还是让其保持层级关系,没有用新的面

图2

板来表示,这样大大的减少了处理的复杂度。文章末尾依然有工程文件的下载地址,如果不想看我下面的介绍可以直接下载试试效果。

二、实现

2.1、搜素框实现

    1)搜素

    搜素框就是一个InputField+Button两控件,点击搜素获取焦点后在其内容变化的事件绑定一个处理方法,代码如下:

UImanger类
{    
...   
 //搜索框输入内容的事件
    public void IFSearchChangeValue()
    {
        if (IFSearcher.isFocused)
        {
            for (int i = 0; i < listAllLevelPartObj.Count; i++)
            {
                BaseLevelPartObj tempBLPO = listAllLevelPartObj[i];
            //    tempBLPO.SearcherUnActive();
                if (tempBLPO.TextTitle.text.Contains(IFSearcher.text))
                {
                    tempBLPO.Btn_PointUp();
                  //  tempBLPO.SearcherMatch();
                  tempBLPO.SearcherSelectedUnFlod();
                }
            }
        }
        else
        {

        }
        bool tempIsNull = IFSearcher.text == "";
        BtnCloseSerach.gameObject.SetActive(!tempIsNull);
        if (tempIsNull)
        {
            //for (int i = 0; i < listAllLevelPartObj.Count; i++)
            //{
            //    BaseLevelPartObj tempBLPO = listAllLevelPartObj[i];
            //    tempBLPO.RestoreFromSearcher();

            //}
        }
    }
}

首先,用列表存储所有创建的UI项(除了第一个物体外)在搜素框输入内容的时候遍历该列表。然后,判断输入的内容和当前项是否匹配。

2)搜素到的项进行展开

     上面已经实现了搜索框的搜素,在搜素到内容的时候会自动展开其所有父菜单和祖父菜单等等的上级菜单,直到穷尽所有的父类。实现这个就是一个递归函数,如下:

UImanger类
{    


.....

    /// <summary>
    /// 是否折叠子列表
    /// </summary>
    /// <param name="isFold"></param>
    public void Btn_FoldSubList(bool isFold)
    {
        if (numSubItems == 0) return;//没有子菜单就无所谓展开与否了
        M_IsFold = isFold;
        ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;
        tempSubItemsParent.enabled = false;
        M_SubObjParentObj.gameObject.SetActive(!isFold);
        SetFoldImage(isFold);
        float tempSign = isFold ? -1 : 1;
        if (isFold)
        {
            M_MySelfRectT.sizeDelta = PriFloldSizeDelta;
        }
        else
        {
            CurUnFoldSizeDelta = PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);
            M_MySelfRectT.sizeDelta = CurUnFoldSizeDelta;
        }
        tempSubItemsParent.enabled = true;
        //给父物体也增加高度并且刷新
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.SubItemFoldAddHeight(tempSign * M_HeightAllSubItems);
        }
        //Debug.Log(TextTitle.text);
    }
    //搜索项选中的时候自动展开其所有父类
    public bool SearcherSelectedUnFlod()
    {
        bool isOver = false;
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.SearcherSelectedUnFlod();
        }
        if(M_IsFold)
        Btn_FoldSubList(false);
        return isOver;
    }
}

 采用一个递归方法,不断的去展开父类的父类,知道 没有父类为止。

  BaseLevelPartObj类
{

....


  /// <param name="isFold"></param>
    public void Btn_FoldSubList(bool isFold)
    {
        M_IsFold = isFold;
        ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;
        tempSubItemsParent.enabled = false;
        M_SubObjParentObj.gameObject.SetActive(!isFold);
        SetFoldImage(isFold);
        float tempSign = isFold ? -1 : 1;
        if (isFold)
        {
            M_MySelfRectT.sizeDelta = PriFloldSizeDelta;
        }
        else
        {
            M_MySelfRectT.sizeDelta = PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);
        }
        tempSubItemsParent.enabled = true;
        //给父物体也增加高度并且刷新
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.SubItemFoldAddHeight(tempSign*M_HeightAllSubItems);
        }
    }
    //搜索项选中的时候自动展开其所有父类
    public bool SearcherSelectedUnFlod()
    {
        bool isOver = false;
        if (null != M_MyParetnObj)
        {
            if (M_MyParetnObj.M_IsFold) M_MyParetnObj.Btn_FoldSubList(!M_MyParetnObj.M_IsFold);
            return M_MyParetnObj.SearcherSelectedUnFlod();
        }
        if (M_IsFold) Btn_FoldSubList(!M_IsFold);
        return isOver;
    }
...
}

2.3、展开之后定位到这个选中项目

       如果搜素结果出来了,已经选中了某个物体,我们还需要将ScollView定位到这个元素,如图3所示,在搜索得到选中的这个物体时,获取其背景条物体的世界坐标,注意不是该物体的坐标,因为选中物体的坐标是不准确的,它有可能不是你看到的那样在

图3

选中的位置处,这是因为它本身有子物体会影响其中心点,从而造成坐标值的变化,只有用背景图的坐标才是准确的,如图4a所示,在选中项目时如果其Content中有子物体,则其坐标中心点会发生偏移

图4a

 

而使用如图4b中的“LeftToolBarList”作为该项获取坐标的物体时,其中心点始终时显示在UI上的项的中心点。这样得到的项的坐标值就是我们想要的,然后用这个坐标值进行转换得到ScrollView的Content下,最后将值直接赋给ScrollView的Content局部坐标值

图4b

就可以得到定位后ScrollView的Content应该在的位置,这个位置是可以保证看到选中的物体的。

                    RectTransform tempContent = ParentPartFirstLevel.GetComponent<RectTransform>();
                    Vector3 tempVecCotent = tempContent.localPosition;
                    float tempOffsetHeight = tempContent.sizeDelta.y - ScrollViewRect.sizeDelta.y;
                    float tempSelectY = tempContent.parent.InverseTransformPoint(tempBLPO.LeftToolBar.position).y;
                    //Debug.Log("选中的高度:" + tempSelectY + "滚轮的高度:" + tempVecCotent.y);
                    ParentPartFirstLevel.transform.DOLocalMoveY(-tempSelectY, 0.35f);

2.4、完整的两个类的代码

 层级菜单项

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

/// <summary>
/// 层级菜单项
/// </summary>
public class BaseLevelPartObj : MonoBehaviour
{
    public Transform LeftToolBar;//该项的左边工具栏
    public static BaseLevelPartObj CurSelectedBLPO;
    public Text TextTitle;//标题
    public Image ImageBtnBg;//背景按钮
    public Image[] ImageBtnFold;//折叠按钮0时折叠,1是展开
    public RectTransform M_MySelfRectT { get; private set; }
    public ContentSizeFitter M_SubObjParentObj { get; private set; }//下级菜单的父物体
    public BaseLevelPartObj M_MyParetnObj { get; private set; }//我的父物体
    public PartsLevel M_Level { get; private set; }//我的层级
    public float M_HeightAllSubItems { get; private set; } = 0;//整个子列表的加起来的高度,包括所有的子孙项
    public bool M_IsBeSelected { get;set; }
    private Vector2 PriFloldSizeDelta;//折叠时候的位置
    private Vector2 CurUnFoldSizeDelta;//当前展开的位置
    public bool M_IsFold { get; private set; } = true;//是否折叠
    private static float TimeLastClick;//上次点中的时间
    private int numSubItems = 0;//子项的数目
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private void SetFoldImage(bool isFold)
    {
        if (numSubItems == 0) return;//没有子菜单就无所谓展开与否了
        ImageBtnFold[0].gameObject.SetActive(!isFold);
        ImageBtnFold[1].gameObject.SetActive(isFold);
    }
    public void AddAllSubItemHeight(float height)
    {
        M_HeightAllSubItems += height;
    }
    /// <summary>
    /// 是否折叠子列表
    /// </summary>
    /// <param name="isFold"></param>
    public void Btn_FoldSubList(bool isFold)
    {
        if (numSubItems == 0) return;//没有子菜单就无所谓展开与否了
        M_IsFold = isFold;
        ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;
        tempSubItemsParent.enabled = false;
        M_SubObjParentObj.gameObject.SetActive(!isFold);
        SetFoldImage(isFold);
        float tempSign = isFold ? -1 : 1;
        if (isFold)
        {
            M_MySelfRectT.sizeDelta = PriFloldSizeDelta;
        }
        else
        {
            CurUnFoldSizeDelta = PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);
            M_MySelfRectT.sizeDelta = CurUnFoldSizeDelta;
        }
        tempSubItemsParent.enabled = true;
        //给父物体也增加高度并且刷新
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.SubItemFoldAddHeight(tempSign * M_HeightAllSubItems);
        }
        //Debug.Log(TextTitle.text);
    }
    //搜索项选中的时候自动展开其所有父类
    public bool SearcherSelectedUnFlod()
    {
        bool isOver = false;
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.SearcherSelectedUnFlod();
        }
        if(M_IsFold)
        Btn_FoldSubList(false);
        return isOver;
    }
    /// <summary>
    /// 子菜单展开带来的高度增加
    /// </summary>
    public void SubItemFoldAddHeight(float height)
    {
        AddAllSubItemHeight(height);
        ContentSizeFitter tempSubItemsParent = M_MyParetnObj == null ? UIManger.M_Instance.ParentPartFirstLevel : M_MyParetnObj.M_SubObjParentObj;
        //如果是一级列表则是最上面的那个父物体
        tempSubItemsParent.enabled = false;
        CurUnFoldSizeDelta= PriFloldSizeDelta + new Vector2(0, M_HeightAllSubItems);
        M_MySelfRectT.sizeDelta = CurUnFoldSizeDelta;
        tempSubItemsParent.enabled = true;
    }
    public virtual void Btn_PointEnter()
    {
        if (M_IsBeSelected) return;
        ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[1];
    }
    public virtual void Btn_PointExit()
    {
        if (M_IsBeSelected) return;
        ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[0];
    }
    public virtual void Btn_PointUp()
    {
        if(null!= CurSelectedBLPO&& CurSelectedBLPO!=this)
        {
            CurSelectedBLPO.M_IsBeSelected = false;
            CurSelectedBLPO.ImageBtnBg.color= UIManger.M_Instance.ColorBtnSelf[0];
        }
        CurSelectedBLPO = this;
     //   UIManger.M_Instance.SelectedBLPobj(this);
        M_IsBeSelected = true;
        ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[2];
    }
    public virtual void Btn_PointDown()
    {
        if(M_IsBeSelected)
        {
            if(Time.time-TimeLastClick<0.2f)
            {
                //   Debug.Log("点击了一次!");
            }
            TimeLastClick = Time.time;
        }
        ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[3];

        RectTransform tempContent = UIManger.M_Instance.ParentPartFirstLevel.GetComponent<RectTransform>();
        Vector3 tempVecCotent = tempContent.localPosition;
        float tempOffsetHeight = tempContent.sizeDelta.y - UIManger.M_Instance.ScrollViewRect.sizeDelta.y;
        float tempSelectY = tempContent.parent.InverseTransformPoint(LeftToolBar.position).y;
        //Debug.Log("选中的高度:" + tempSelectY + "滚轮的高度:" + tempVecCotent.y);
    }
    //有子物体了,设置其折叠按钮
    public void AddSubObj(int num)
    {
        numSubItems += num;
        SetFoldImage(true);
    }
    //搜素失活状态
    public void SearcherUnActive()
    {
        SetParetn(null);
        gameObject.SetActive(false);
        UnActivePriVec = M_MySelfRectT.sizeDelta;
        M_MySelfRectT.sizeDelta = PriFloldSizeDelta;//失活的时候让每个项都保持在折叠的状态
    }
    Vector2 UnActivePriVec;
    //从搜素中恢复
    public void RestoreFromSearcher()
    {
        if (numSubItems > 0)
        {
            SetFoldImage(M_IsFold);
        }
        gameObject.SetActive(true);
        SetParetn(M_MyParetnObj);
        M_MySelfRectT.sizeDelta = UnActivePriVec;
    }
    //搜素匹配到
    public void SearcherMatch()
    {
        //先激活物体
        gameObject.SetActive(true);
        //折叠、展开按钮都失活
        ImageBtnFold[0].gameObject.SetActive(false);
        ImageBtnFold[1].gameObject.SetActive(false);
    }
    public void SetParetn(BaseLevelPartObj parent)
    {
        if (null != parent)
        {
            transform.SetParent(parent.M_SubObjParentObj.transform);
        }
        else
        {
            //如果是一级菜单则其父物体为最上面的内容管理器
            transform.SetParent(UIManger.M_Instance.ParentPartFirstLevel.transform);
        }
        transform.localScale = Vector3.one;
        transform.localPosition = Vector3.zero;
    }
 
    public virtual void Init(string partName,BaseLevelPartObj parent, PartsLevel level)
    {
        ImageBtnBg.color = UIManger.M_Instance.ColorBtnSelf[0];//初始化背景图
        M_SubObjParentObj = GetComponentInChildren<ContentSizeFitter>(true);
        M_MySelfRectT = GetComponent<RectTransform>();
        PriFloldSizeDelta = M_MySelfRectT.sizeDelta;
        CurUnFoldSizeDelta = PriFloldSizeDelta;
        M_MyParetnObj = parent;
        SetParetn(parent);
         M_Level = level;
        TextTitle.text = partName;
        //SetLevelShow();
        ImageBtnFold[0].gameObject.SetActive(false);
        ImageBtnFold[1].gameObject.SetActive(false);
        if (null != M_MyParetnObj)
        {
            M_MyParetnObj.M_SubObjParentObj.gameObject.SetActive(false); 一开始除了一级菜单都不显示
            M_MyParetnObj.AddAllSubItemHeight(M_MySelfRectT.rect.height);//增加当前物体的父物体子列表高度
        }
    }
}

控制项目

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

public class UIManger : MonoBehaviour
{
    public Button BtnCloseSerach;
    public InputField IFSearcher;
    public BaseLevelPartObj PrefabsPartObj;
    public Transform ReadObj;
    public Color[] ColorBtnSelf;
    public RectTransform ScrollViewRect;
    public static UIManger M_Instance
    {
        get
        {
            if (null == instance)
            {
                instance = FindObjectOfType<UIManger>();
            }
            return instance;
        }
    }
    public ContentSizeFitter ParentPartFirstLevel;//一级菜单的父物体
    private List<BaseLevelPartObj> listAllLevelPartObj = new List<BaseLevelPartObj>();
    private static UIManger instance;
    // Start is called before the first frame update
    void Start()
    {
        BtnCloseSerach.gameObject.SetActive(false);
        //先缓存一级和二级的菜单项
        Dictionary<string, BaseLevelPartObj> tempDicFirstLevelObj = new Dictionary<string, BaseLevelPartObj>();
        Dictionary<string, BaseLevelPartObj> tempDicSecLevelObj = new Dictionary<string, BaseLevelPartObj>();
        Transform[] tempTransObj = ReadObj.GetComponentsInChildren<Transform>(true);
        //采用一次遍历创建完所有的层级菜单项
        foreach (Transform item in tempTransObj)
        {
            BaseLevelPartObj tempBLPObj = Instantiate(PrefabsPartObj);
            PartsLevel tempLevel = (PartsLevel)(GetLevel(item) - 1);
          //  Debug.Log(item.name + ":" + GetLevel(item) + ":p:"+(item.parent==null?"null":item.parent.name));
            switch (tempLevel)
            {
                case PartsLevel.None:
                    Destroy(tempBLPObj.gameObject);
                    continue;
                case PartsLevel.First:
                    tempBLPObj.Init(item.name, null, tempLevel);
                    tempDicFirstLevelObj.Add(item.name, tempBLPObj);
                    break;
                case PartsLevel.Second:
                    //如果临时一级菜单中有包含当前物体的父物体
                    if (tempDicFirstLevelObj.ContainsKey(item.parent.name))
                    {
                        BaseLevelPartObj tempParentObj = tempDicFirstLevelObj[item.parent.name];
                        tempParentObj.AddSubObj(1);
                        tempBLPObj.Init(item.name, tempParentObj, tempLevel);
                        tempDicSecLevelObj.Add(item.name, tempBLPObj);
                    }
                    break;
                case PartsLevel.Third:
                    //如果临时的二级菜单中有包含当前物体的父物体
                    if (tempDicSecLevelObj.ContainsKey(item.parent.name))
                    {
                        BaseLevelPartObj tempParentObj = tempDicSecLevelObj[item.parent.name];
                        tempParentObj.AddSubObj(1);
                        tempBLPObj.Init(item.name, tempParentObj, tempLevel);
                    }
                    break;
            }
            listAllLevelPartObj.Add(tempBLPObj);
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    //搜索框输入内容的事件
    public void IFSearchChangeValue()
    {
        if (IFSearcher.isFocused)
        {
            for (int i = 0; i < listAllLevelPartObj.Count; i++)
            {
                BaseLevelPartObj tempBLPO = listAllLevelPartObj[i];
                //    tempBLPO.SearcherUnActive();
                if (tempBLPO.TextTitle.text.Contains(IFSearcher.text))
                {
                    tempBLPO.Btn_PointUp();
                    //  tempBLPO.SearcherMatch();
                    tempBLPO.SearcherSelectedUnFlod();

                    RectTransform tempContent = ParentPartFirstLevel.GetComponent<RectTransform>();
                    Vector3 tempVecCotent = tempContent.localPosition;
                    float tempOffsetHeight = tempContent.sizeDelta.y - ScrollViewRect.sizeDelta.y;
                    float tempSelectY = tempContent.parent.InverseTransformPoint(tempBLPO.LeftToolBar.position).y;
                    //Debug.Log("选中的高度:" + tempSelectY + "滚轮的高度:" + tempVecCotent.y);
                    ParentPartFirstLevel.transform.DOLocalMoveY(-tempSelectY, 0.35f);
                    break;
                }
            }
        }
        else
        {

        }
        bool tempIsNull = IFSearcher.text == "";
        BtnCloseSerach.gameObject.SetActive(!tempIsNull);
        if (tempIsNull)
        {
            //for (int i = 0; i < listAllLevelPartObj.Count; i++)
            //{
            //    BaseLevelPartObj tempBLPO = listAllLevelPartObj[i];
            //    tempBLPO.RestoreFromSearcher();

            //}
        }
    }
    //清空搜索框
    public void Btn_ClearSearcher()
    {
        IFSearcher.text = "";
        //  SelectedBLPobj(null);
        BtnCloseSerach.gameObject.SetActive(false);

    }
    //获取当前的层级
    public int GetLevel(Transform transfor)
    {
        int tempLevel = 1;

        if(transfor.parent!=null)
        {
            return tempLevel+ GetLevel(transfor.parent);
        }
        return tempLevel;
    }
}
public enum PartsLevel
{
    None = 0,
    First = 1,
    Second,
    Third,
}

三、总结

3.1、重点是处理搜素后的展开,采用递归实现先展开最上面的层级,然后一级一级往下展开,类似于手动点开;

3.2、搜索后的定位,一定要注意获取项目的坐标值时应该选取项的哪个部分;

3.2、工程文件下载地址

以上是关于Unity实用小工具或脚本——可折叠伸缩的多级列表二(带搜索功能)的主要内容,如果未能解决你的问题,请参考以下文章

Unity实用小工具或脚本——可折叠伸缩的多级(至少三级)内容列表(类似于Unity的Hierarchy视图中的折叠效果)

游戏开发解答Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)

游戏开发解答Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)

游戏开发解答Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)

在保持 N 级或多级可展开列表视图的折叠/展开状态后,某些子组不会显示

Unity实用小工具或脚本—以对象方式访问MySql数据库