一、前言

继上篇可折叠伸缩的多级(至少三级)内容列表实现了类似于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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid 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 framevoid 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实用小工具或脚本——可折叠伸缩的多级列表二(带搜索功能)相关推荐

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

    目录 一.前言 二.实现 2.1.创建ScrollView 2.2.制作层级预设体BaseLevelPartObj 2.3.设置该预设体的初始化处理方法 2.4.读取Hierarchy的内容并创建UI ...

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

    一.前言         以对象方式处理MySql数据库顾名思义就是可以将每个数据库表作为一个类,没一条数据作为一个对象来进行操作,大致思路和我上一篇文章类似,这里不再赘述.文章后有资源下载地址,所使 ...

  3. Unity实用小工具或脚本——录屏工具

    一.前言 本文要讲的录屏不是使用Unity自带的那个截屏方法,因为unity自带的都只能截取unity程序本身显示的画面内容,至于unity程序之外的内容,如电脑桌面上的其他的程序内容是无法录屏的.本 ...

  4. Unity实用小工具或脚本—3D炫酷UI篇(一)

    一.前言 最近做AR和VR的项目,经常需要用到3D的UI,特意将最近自己捣鼓的这个UI的东西写下来.效果如图所示:主要的动画和素材也是借鉴了 第三方的插件"HoloUIExample&quo ...

  5. Unity实用小工具或脚本——读写Json工具

    一.前言       在Unity中读写Json文件已经有非常好的工具,可以将Json文件和结构体数据进行相互转换,如图1所示,在Unity Asset Store中搜JSON.NET可以找到该插件, ...

  6. Unity实用小工具或脚本——智能包住任意多个物体的碰撞体

    一.前言 有些模型是我们在Unity中进行组合得到的一个模块,这个时候不可能再让美术给加碰撞体,需要用代码智能给其加碰撞体,如图1所示:任意的多个物体组成的一个综合物体最后都可以给它加上合适的碰撞体, ...

  7. Unity实用小工具或脚本——XML工具

    一.前言 这篇文章,早在游戏蛮牛的专栏里就写过,一直在使用这个工具,稍微修改了下.在我们的项目中经常会用到XML来存储一些工程中的设置,而对XML的操作无非就是增删改查四个操作,代码虽然不多,但是使用 ...

  8. Unity实用小工具或脚本——3D物体带坐标轴的拖拽

    一.前言 我们最近要做一个线路的规划编辑,并且是在三维场景中,编辑完就立马能用.立马能用还好说,有特别多的轮子可以用,在三维场景中实时编辑就有点意思了.其实功能就是类似于在Unity的编辑界面操作一个 ...

  9. Unity实用小工具—以对象形式操作Sqlite

    一.介绍 1.1.版本说明:使用的Unity版本为个人版2020.3.39,数据库为Sqlite(一个轻量级跨平台数据库),Sqlite的查看管理工具可以在网上下载https://dbeaver.io ...

最新文章

  1. git 查看某个文件的历史记录
  2. CMS GC:CMS 废弃了,该怎么办呢?
  3. vue App.vue router 过渡效果, keep-alive 结合使用示例
  4. android 自定义checkBox的样式
  5. [SpringSecurity]web权限方案_用户认证_设置用户名密码
  6. Windows Phone开发(11):常用控件(下)
  7. UVA10602 Editor Nottoobad【贪心】
  8. 第三组视频:基于OCFS2的双主DRBD
  9. centos llvm安装_CentOS7.x安装LLVM6.0
  10. 虚拟机启动时出现operating system not found如何解决?
  11. 计算机科学导论简答题答案题库,计算机科学导论习题答案
  12. python制作编程软件的方法_利用python程序生成word和PDF文档的方法
  13. [从头读历史] 第262节 左传 [BC597至BC538]
  14. Python线性分类
  15. 解决qrcode生成的二维码微信长按不识别问题
  16. 东京奥运会将采用人脸识别系统 加强安检
  17. 未来生活里掌握计算机技术的重要性,浅谈计算机教育重要性的几点理解.doc
  18. 培训机构到底好不好?学历重要吗?java
  19. linux怎么创建用户登录,如何创建一个不能登录的Linux用户名
  20. RAFT:使用深度学习的光流估计

热门文章

  1. 数据分析八大模型:OGSM模型
  2. 仿抖音短视频APP双端系统源码+带教程/PHP的
  3. linux创建用户张飞,Linux系统操作测试试题 部分
  4. 8本入门级大数据经典图书,开启你的“深度学习” | 世界读书日
  5. RocketMq最强总结 带你rocket从入门到入土为安
  6. idea创建maven工程没有src文件夹,或者是maven文件结构不能完整创建,可能是因为你的网速问题
  7. (2018 -NIPS)SimplE embedding for link prediction in knowledge
  8. 护眼灯真能护眼吗?学习专用的护眼灯推荐
  9. 配置 FoxyProxy 规则自由切换代理模式
  10. 机器学习与深度学习到底有什么区别