Unity无限滚动的滚动显示组件,可以 折叠/打开 节点的滚动显示组件。
最近在开发一个加载模型的功能,自然用到了选中物体的面板。
类似Unity的Hierarchy(层级视图),点击UI同时也选中物体,点击物体反过来也选中了UI。
加载模型的时候展示模型节点信息,最开始直接使用ScrollView组件。
每个Transform组件就是一个节点,这导致这个模型子物体有成千上万个的时候,节点生成的巨多,导致Drawcall巨多,卡成屎。
然后想到之前见过的无限滚动ScrollView,想过直接用别人造的轮子跑,但是找了好多跟我想要达到的功能并不一致。
得了,自己造吧。
先展示一个动图:
点选节点可以实现对应模型的操作。按住Ctrl可以多选。
首先要有个大致思路:
有两个问题需要解决:1.怎么能让节点列表折叠 / 打开显示?2.怎么让节点无限利用起来?
第一个问题:我这里用一个List储存一下所有的节点,再用一个List储存一下所有的可见节点。
/// <summary>
/// 所有的节点
/// </summary>public List<NodeItemSerializable> allNodesInfo = new List<NodeItemSerializable>();
/// <summary>
/// 当前可以显示的节点
/// </summary>
List<NodeItemSerializable> canShowNodes = new List<NodeItemSerializable>();
每次当有折叠/打开的操作时候,把canShowNodes列表刷新一下就解决了这个问题。
第二个问题:卡顿,原因就是,节点太多,那么不生成看不到的节点就可了,每次动态调整场景中的可见节点。
至于动态的滑动:
当滑条向下滑的时候将顶部的物体超出视野的拿走,同时将底部的空缺使用顶部拿走的物体补上。
当滑条向上滑的时候,反之。
先用一张图理解一下:
所以真正在场景中用户可见的节点列表就是上图黄色区域。
向下滑动,
就从黄色区域第一个节点,塞进绿色区域最后一个位置,
再从红色区域拿第一个节点,塞到黄色区域最后一个位置。(脑海中浮现了人体蜈蚣,哈哈哈)
理解了图片,我们再来看动态更改列表的操作:
/// <summary>
/// 当前显示的物体最后一个所引值
/// </summary>
public int curShowObjLastIndex;
这里用了一个int变量记录一下最后一个显示在列表中的节点索引值。
/// <summary>
/// 在可显示物体之前的节点信息
/// </summary>
Stack<NodeItemSerializable> beforeNodes = new Stack<NodeItemSerializable>();
/// <summary>
/// 在可显示物体之后的节点信息
/// </summary>
Stack<NodeItemSerializable> afterNodes = new Stack<NodeItemSerializable>();
这两个变量用了Stack,利用栈的特性,先进先出,方便存取信息。
下面的offsetY就是获取的鼠标滑动操作: offsetY = -Input.GetAxis("Mouse ScrollWheel") * 1000;
if (offsetY > 0)
{
//这里的 cueShowObjLastIndex 是在场景中节点的最后一位索引
if (curShowObjLastIndex >= canShowNodes.Count)
{
offsetY = 0;
}
//Debug.Log("向上拽,item向下移动,最上头的item给before,最下层从after取出");
float tempY = parent.anchoredPosition.y;
tempY += offsetY;
if (offsetY != 0 && tempY >= tempItemHeight && afterNodes.Count > 0)
{
beforeNodes.Push(canShowNodes[curShowObjLastIndex - allNodeObjs.Count]);
DestroyImmediate(allNodeObjs[0]);
allNodeObjs.RemoveAt(0);
GameObject tempAddGo = Instantiate(tempChildRect.gameObject, parent);
SetNodeObjInfo(tempAddGo, afterNodes.Pop());
allNodeObjs.Add(tempAddGo);
tempY = 0;
curShowObjLastIndex += 1;
SetScrollBarValue();
}
parent.anchoredPosition = new Vector2(parent.anchoredPosition.x, tempY);
}
else if (offsetY < 0)
{
//当前索引减去总显示物体数量==0就代表拉到最顶端了
if (curShowObjLastIndex - allNodeObjs.Count <= 0)
{
offsetY = 0;
}
//Debug.Log("向下拽" + offsetY);
float tempY = parent.anchoredPosition.y;
tempY += offsetY;
if (offsetY != 0 && tempY <= -tempItemHeight && beforeNodes.Count > 0)
{
afterNodes.Push(canShowNodes[curShowObjLastIndex - 1]);
DestroyImmediate(allNodeObjs[allNodeObjs.Count - 1]);
allNodeObjs.RemoveAt(allNodeObjs.Count - 1);
GameObject tempAddGo = Instantiate(tempChildRect.gameObject, parent);
tempAddGo.transform.SetAsFirstSibling();
SetNodeObjInfo(tempAddGo, beforeNodes.Pop());
allNodeObjs.Insert(0, tempAddGo);
tempY = 0;
curShowObjLastIndex -= 1;
SetScrollBarValue();
}
parent.anchoredPosition = new Vector2(parent.anchoredPosition.x, tempY);
}
这里不断的使用destroy和Instantiate,也会造成性能下降,可以搞个对象池解决。
源码:https://github.com/wtb521thl/InfiniteRollingScrollViewDemo
就这样。拜拜~