文章目录
- 1. 参考文章
- 2. 工程地址
- 3. 项目结构
- 4. 主要代码
1. 参考文章
https://blog.csdn.net/qq992817263/article/details/54925472
2. 工程地址
将文件夹放入 unity 中即可查看
作者 github 地址:https://github.com/ccUnity3d/TreeView
本人 gitee 地址(不用翻墙):https://gitee.com/hwm-ming/unity-tree-view
3. 项目结构
4. 主要代码
TreeViewControl(控制类)
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
/// <summary>
/// 树形菜单控制器
/// </summary>
public class TreeViewControl : MonoBehaviour
{
/// <summary>
/// 当前树形菜单的数据源
/// </summary>
[HideInInspector]
public List<TreeViewData> Data = null;
/// <summary>
/// 树形菜单中元素的模板
/// </summary>
public GameObject Template;
/// <summary>
/// 树形菜单中元素的根物体
/// </summary>
public Transform TreeItems;
/// <summary>
/// 树形菜单的纵向排列间距
/// </summary>
public int VerticalItemSpace = 2;
/// <summary>
/// 树形菜单的横向排列间距
/// </summary>
public int HorizontalItemSpace = 25;
/// <summary>
/// 树形菜单中元素的宽度
/// </summary>
public int ItemWidth = 230;
/// <summary>
/// 树形菜单中元素的高度
/// </summary>
public int ItemHeight = 35;
/// <summary>
/// 所有子元素的鼠标点击回调事件
/// </summary>
public delegate void ClickItemdelegate(GameObject item);
public event ClickItemdelegate ClickItemEvent;
//当前树形菜单中的所有元素
private List<GameObject> _treeViewItems;
//当前树形菜单中的所有元素克隆体(刷新树形菜单时用于过滤计算)
private List<GameObject> _treeViewItemsClone;
//树形菜单当前刷新队列的元素位置索引
private int _yIndex = 0;
//树形菜单当前刷新队列的元素最大层级
private int _hierarchy = 0;
//正在进行刷新
private bool _isRefreshing = false;
void Awake()
{
ClickItemEvent += ClickItemTemplate;
}
/// <summary>
/// 鼠标点击子元素事件
/// </summary>
public void ClickItem(GameObject item)
{
ClickItemEvent(item);
}
void ClickItemTemplate(GameObject item)
{
//空的事件,不这样做的话ClickItemEvent会引发空引用异常
}
/// <summary>
/// 返回指定名称的子元素是否被勾选
/// </summary>
public bool ItemIsCheck(string itemName)
{
for (int i = 0; i < _treeViewItems.Count; i++)
{
if (_treeViewItems[i].transform.Find("TreeViewText").GetComponent<Text>().text == itemName)
{
return _treeViewItems[i].transform.Find("TreeViewToggle").GetComponent<Toggle>().isOn;
}
}
return false;
}
/// <summary>
/// 返回树形菜单中被勾选的所有子元素名称
/// </summary>
public List<string> ItemsIsCheck()
{
List<string> items = new List<string>();
for (int i = 0; i < _treeViewItems.Count; i++)
{
if (_treeViewItems[i].transform.Find("TreeViewToggle").GetComponent<Toggle>().isOn)
{
items.Add(_treeViewItems[i].transform.Find("TreeViewText").GetComponent<Text>().text);
}
}
return items;
}
/// <summary>
/// 生成树形菜单
/// </summary>
public void GenerateTreeView()
{
//删除可能已经存在的树形菜单元素
if (_treeViewItems != null)
{
for (int i = 0; i < _treeViewItems.Count; i++)
{
Destroy(_treeViewItems[i]);
}
_treeViewItems.Clear();
}
//重新创建树形菜单元素
_treeViewItems = new List<GameObject>();
for (int i = 0; i < Data.Count; i++)
{
GameObject item = Instantiate(Template);
if (Data[i].ParentID == -1)
{
item.GetComponent<TreeViewItem>().SetHierarchy(0);
item.GetComponent<TreeViewItem>().SetParent(null);
}
else
{
TreeViewItem tvi = _treeViewItems[Data[i].ParentID].GetComponent<TreeViewItem>();
item.GetComponent<TreeViewItem>().SetHierarchy(tvi.GetHierarchy() + 1);
item.GetComponent<TreeViewItem>().SetParent(tvi);
tvi.AddChildren(item.GetComponent<TreeViewItem>());
}
item.transform.name = "TreeViewItem";
item.transform.Find("TreeViewText").GetComponent<Text>().text = Data[i].Name;
item.transform.SetParent(TreeItems);
item.transform.localPosition = Vector3.zero;
item.transform.localScale = Vector3.one;
item.transform.localRotation = Quaternion.Euler(Vector3.zero);
item.SetActive(true);
_treeViewItems.Add(item);
}
foreach (var item in _treeViewItems)
{
if (item.GetComponent<TreeViewItem>().GetChildrenNumber() == 0)
{
item.transform.Find("ContextButton").gameObject.SetActive(false);
}
}
}
/// <summary>
/// 刷新树形菜单
/// </summary>
public void RefreshTreeView()
{
//上一轮刷新还未结束
if (_isRefreshing)
{
return;
}
_isRefreshing = true;
_yIndex = 0;
_hierarchy = 0;
//复制一份菜单
_treeViewItemsClone = new List<GameObject>(_treeViewItems);
//用复制的菜单进行刷新计算
for (int i = 0; i < _treeViewItemsClone.Count; i++)
{
//已经计算过或者不需要计算位置的元素
if (_treeViewItemsClone[i] == null || !_treeViewItemsClone[i].activeSelf)
{
continue;
}
TreeViewItem tvi = _treeViewItemsClone[i].GetComponent<TreeViewItem>();
_treeViewItemsClone[i].GetComponent<RectTransform>().localPosition = new Vector3(tvi.GetHierarchy() * HorizontalItemSpace, _yIndex,0);
_yIndex += (-(ItemHeight + VerticalItemSpace));
if (tvi.GetHierarchy() > _hierarchy)
{
_hierarchy = tvi.GetHierarchy();
}
//如果子元素是展开的,继续向下刷新
if (tvi.IsExpanding)
{
RefreshTreeViewChild(tvi);
}
_treeViewItemsClone[i] = null;
}
//重新计算滚动视野的区域
float x = _hierarchy * HorizontalItemSpace + ItemWidth;
float y = Mathf.Abs(_yIndex);
transform.GetComponent<ScrollRect>().content.sizeDelta = new Vector2(x, y);
//清空复制的菜单
_treeViewItemsClone.Clear();
_isRefreshing = false;
}
/// <summary>
/// 刷新元素的所有子元素
/// </summary>
void RefreshTreeViewChild(TreeViewItem tvi)
{
for (int i = 0; i < tvi.GetChildrenNumber(); i++)
{
tvi.GetChildrenByIndex(i).gameObject.GetComponent<RectTransform>().localPosition = new Vector3(tvi.GetChildrenByIndex(i).GetHierarchy() * HorizontalItemSpace, _yIndex, 0);
_yIndex += (-(ItemHeight + VerticalItemSpace));
if (tvi.GetChildrenByIndex(i).GetHierarchy() > _hierarchy)
{
_hierarchy = tvi.GetChildrenByIndex(i).GetHierarchy();
}
//如果子元素是展开的,继续向下刷新
if (tvi.GetChildrenByIndex(i).IsExpanding)
{
RefreshTreeViewChild(tvi.GetChildrenByIndex(i));
}
int index = _treeViewItemsClone.IndexOf(tvi.GetChildrenByIndex(i).gameObject);
if (index >= 0)
{
_treeViewItemsClone[index] = null;
}
}
}
}
TreeViewItem(子项)
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
/// <summary>
/// 树形菜单元素
/// </summary>
public class TreeViewItem : MonoBehaviour
{
/// <summary>
/// 树形菜单控制器
/// </summary>
public TreeViewControl Controler;
/// <summary>
/// 当前元素的子元素是否展开(展开时可见)
/// </summary>
public bool IsExpanding = false;
//当前元素在树形图中所属的层级
private int _hierarchy = 0;
//当前元素指向的父元素
private TreeViewItem _parent;
//当前元素的所有子元素
private List<TreeViewItem> _children;
//正在进行刷新
private bool _isRefreshing = false;
void Awake()
{
// // 没有子元素隐藏下拉按钮
// if (GetChildrenNumber() == 0)
// {
// transform.Find("ContextButton").gameObject.SetActive(false);
// }
//上下文按钮点击回调
transform.Find("ContextButton").GetComponent<Button>().onClick.AddListener(ContextButtonClick);
transform.Find("TreeViewButton").GetComponent<Button>().onClick.AddListener(delegate () {
Controler.ClickItem(gameObject);
});
}
/// <summary>
/// 点击上下文菜单按钮,元素的子元素改变显示状态
/// </summary>
void ContextButtonClick()
{
//上一轮刷新还未结束
if (_isRefreshing)
{
return;
}
_isRefreshing = true;
if (IsExpanding)
{
transform.Find("ContextButton").GetComponent<RectTransform>().localRotation = Quaternion.Euler(0, 0, 90);
IsExpanding = false;
ChangeChildren(this, false);
}
else
{
transform.Find("ContextButton").GetComponent<RectTransform>().localRotation = Quaternion.Euler(0, 0, 0);
IsExpanding = true;
ChangeChildren(this, true);
}
//刷新树形菜单
Controler.RefreshTreeView();
_isRefreshing = false;
}
/// <summary>
/// 改变某一元素所有子元素的显示状态
/// </summary>
void ChangeChildren(TreeViewItem tvi, bool value)
{
for (int i = 0; i < tvi.GetChildrenNumber(); i++)
{
tvi.GetChildrenByIndex(i).gameObject.SetActive(value);
if (tvi.GetChildrenByIndex(i).IsExpanding)
{
ChangeChildren(tvi.GetChildrenByIndex(i), value);
}
}
}
#region 属性访问
public int GetHierarchy()
{
return _hierarchy;
}
public void SetHierarchy(int hierarchy)
{
_hierarchy = hierarchy;
}
public TreeViewItem GetParent()
{
return _parent;
}
public void SetParent(TreeViewItem parent)
{
_parent = parent;
}
public void AddChildren(TreeViewItem children)
{
if (_children == null)
{
_children = new List<TreeViewItem>();
}
_children.Add(children);
}
public void RemoveChildren(TreeViewItem children)
{
if (_children == null)
{
return;
}
_children.Remove(children);
}
public void RemoveChildren(int index)
{
if (_children == null || index < 0 || index >= _children.Count)
{
return;
}
_children.RemoveAt(index);
}
public int GetChildrenNumber()
{
if (_children == null)
{
return 0;
}
return _children.Count;
}
public TreeViewItem GetChildrenByIndex(int index)
{
if (index >= _children.Count)
{
return null;
}
return _children[index];
}
#endregion
}
TreeViewData(数据类)
/// <summary>
/// 树形菜单数据
/// </summary>
public class TreeViewData
{
/// <summary>
/// 数据内容
/// </summary>
public string Name;
/// <summary>
/// 数据所属的父ID
/// </summary>
public int ParentID;
}