基于Unity·UGUI实现的RecycleList循环列表UI容器
在UI功能开发实践中,列表UI容器是我们经常使用一种UI容器组件。这种组件就根据输入的数据集合生成对应数据项目。从显示的方向来说,一般就分为水平排布和垂直排布的列表容器两种。列表容器为了在有限的界面空间中显示全部的数据,都会搭配使用UGUI的ScrollRect和Mask组件,我们只需要上下滑动,就可以浏览所要呈现的信息。但是,在UGUI中有几条数据就生成对应条目数的数据视图项,未免有些太过于奢侈。因为,每个数据项目不仅仅是一个UGUI的显示组件,而是多个显示组件(比如几个Text和Image)构成,最终,瞬时需要生成的GameObject总数目将会是数据总是的倍数。比如,我们有100条联系人数据,需要显示人的名称、电话、拨号按钮,背景Image直接挂在作为排列计算的母节点上,那么一个数据项目就需要至少4个GameObject表达。100条的数据将会导致一般的列表容器UI不得不同时生成400个GameObject,而同一时刻生成这么的GameObject,可以想象,Unity的运行时,将迎来一个不小的性能尖峰时刻,甚至我们能感受到界面进入假死状态。针对这个问题,一种比较理想的方案,就是用少量的GameObject去表示庞大的数据,随着滑动Scroll,mask对应的可显示区域,始终都是使用这几个GameObject条目显示当前的数据项。我们只需要不断的更新GameObject对应的列表的位置,已经及时设置起数据内容,就可以达成我们的目的。
原理是这样,那么现在谈及具体的实施细节。要实现这个功能,我们就需要确定用什么样的特征去衡量当前列表的状态。也就是,如何确保我们要怎样移动这些列表项的GameObject(后文将用Cell来指代这个条目的根节点GameObject)到达当前Scroll Position对应的位置,以及当前显示又是哪个条目的数据。
我们先设定列表项目的UI层级结构:
ScrollRect
-> Mask
--> Content
--> Cell 1
--> Cell 2
.........
--> Cell n
ScrollRect设定是只有垂直方向的移动能力,手动添加几个项目到Content中,上下移动ScrollRect,我们就发现Content的Position就会对应有一个移动操作。那么,事情就简单了,Content位移量和Cell的大小我们可以知道。对于垂直方向的表单,如果想知道当前显示的Cell是第几个,只需要拿Content的y值去除以于Cell的高度。那么我们只需要记下当前的index,一旦发现当前index发生变化,那么就可以使用这个变化,去移动Cell到需要的位置,并且也能做到最优先移动和更新看不见的Cell,原有可用的Cell维持不变。
那具体怎么看搬迁的方法,我们先看scroll向上的滑动。显然,这种状态下就是当前第一个cell的index小于Content能够被mask显示项目的currentIndex,那么就是找出哪些都是小于currentIndex的cell,并出队搬迁到cell列表的尾部,然后计算从哪个cell开始需要重新如何新的输入,并计算正确的偏移坐标。同理,scroll向下滑动也一样,只不过要补偿计算mask当前能显示多少个cell,从mask的底部开始计算找出可复用的cell节点。


OK,简单说完方法,我就放出演示的gif动画和代码。这个代码仅仅是实现上上述的功能,但是代码组织上还有可以迭代改进的空间,比如,可否把滑动向上向下的处理合二为一,或者是赋值重新排布的代码统一成一个等等。

using System;
using System.Collections.Generic;
using Neurons.SaturnUI.Data;
using Neurons.Util;
using UnityEngine;
using UnityEngine.UI; namespace Neurons.SaturnUI.UGUI
{
[RequireComponent(typeof(ScrollRect))]
public class SaturnVerticalRecycleListLayoutGroup : MonoBehaviour, ICollectionUIHandler
{
[SerializeField]
private UICell uiCellPrefab;
[SerializeField]
private RectTransform contentRect;
[SerializeField]
private float spacing = 5f; private Vector2 layoutSize;
private Vector2 cellSize;
private int displayContentCount; private List<UIData> datas = new List<UIData>(); private List<UICell> cellCaches = new List<UICell>();
private List<UICell> tmpRemoveds = new List<UICell>(); private int indexOfList; public void SetUIData(List<UIData> newDatas)
{
this.datas.Clear();
this.datas.AddRange(newDatas); UpdateUI();
} public void AddUIData(UIData newData)
{
this.datas.Add(newData); UpdateUI();
} public void RemoveUIData(UIData data)
{
this.RemoveUIData(data); UpdateUI();
} public void CleanUIDatas()
{
this.datas.Clear(); UpdateUI();
} public bool HasUIDatas()
{
return datas != null && datas.Count > ;
} public void UpdateUI()
{
this.contentRect.sizeDelta = new Vector2(this.contentRect.sizeDelta.x, (spacing + cellSize.y) * datas.Count); var currentIndex = (int)(contentRect.localPosition.y / (cellSize.y + spacing));
currentIndex = Math.Min(currentIndex, datas.Count - ); var j = currentIndex;
for (var i = ; j < datas.Count && i < cellCaches.Count; i++, j++)
{
var newCellGo = cellCaches[i]; newCellGo.gameObject.SetActiveEffectively(true);
newCellGo.transform.localPosition = new Vector3(, -j * (spacing + cellSize.y), ); newCellGo.SetUIData(datas[j]);
} indexOfList = currentIndex;
} private void Awake()
{
cellSize = uiCellPrefab.GetComponent<RectTransform>().sizeDelta;
layoutSize = this.GetComponent<RectTransform>().sizeDelta;
displayContentCount = (int)(layoutSize.y / cellSize.y); CreateCellCaches();
} private void Update()
{
var currentIndex = (int)(contentRect.localPosition.y / (cellSize.y + spacing)); if (indexOfList != currentIndex && currentIndex >= )
{
UpdateCellUI(currentIndex);
indexOfList = currentIndex;
}
} private void UpdateCellUI(int currentIndex)
{
if (cellCaches.Count > )
{
var cellIndex = (int)Math.Abs(cellCaches[].transform.localPosition.y / (cellSize.y + spacing)); if (cellIndex < currentIndex)
{
MoveForDown(currentIndex);
}
else if (cellIndex > currentIndex)
{
MoveForUp(currentIndex);
}
}
} private void MoveForUp(int currentIndex)
{
tmpRemoveds.Clear(); for (var i = cellCaches.Count - ; i >= ; i--)
{
var cell = cellCaches[i];
var cellIndex = (int)Math.Abs(cell.transform.localPosition.y / (cellSize.y + spacing)); if (cellIndex > currentIndex + displayContentCount)
{
tmpRemoveds.Add(cell);
}
} for (var i = ; i < tmpRemoveds.Count; i++)
{
cellCaches.Remove(tmpRemoveds[i]);
cellCaches.Insert(, tmpRemoveds[i]);
} var j = ;
for (var i = currentIndex; i < datas.Count && j < cellCaches.Count; i++, j++)
{
var cell = cellCaches[j];
cell.gameObject.SetActiveEffectively(true);
cell.SetUIData(datas[i]);
cell.transform.localPosition = new Vector3(, -i * (spacing + cellSize.y), );
} for (; j < cellCaches.Count; j++)
{
cellCaches[j].gameObject.SetActiveEffectively(false);
}
} private void MoveForDown(int currentIndex)
{
tmpRemoveds.Clear(); for (var i = ; i < cellCaches.Count; i++)
{
var cell = cellCaches[i];
var cellIndex = (int)Math.Abs(cell.transform.localPosition.y / (cellSize.y + spacing)); if (cellIndex < currentIndex)
{
tmpRemoveds.Add(cell);
}
} for (var i = ; i < tmpRemoveds.Count; i++)
{
cellCaches.Remove(tmpRemoveds[i]);
cellCaches.Add(tmpRemoveds[i]);
} var j = cellCaches.Count - tmpRemoveds.Count;
for (var i = currentIndex + cellCaches.Count - tmpRemoveds.Count; i < datas.Count && j < cellCaches.Count; i++, j++)
{
var cell = cellCaches[j];
cell.gameObject.SetActiveEffectively(true);
cell.SetUIData(datas[i]);
cell.transform.localPosition = new Vector3(, -i * (spacing + cellSize.y), );
} for (; j < cellCaches.Count; j++)
{
cellCaches[j].gameObject.SetActiveEffectively(false);
}
} private void CreateCellCaches()
{
var cacheCount = (int)(layoutSize.y / cellSize.y + ); for (var i = ; i < cacheCount; i++)
{
var newCellGo = uiCellPrefab.gameObject.Clone(false); newCellGo.DockTo(contentRect, true);
newCellGo.transform.localPosition = new Vector3(, -i * (spacing + cellSize.y), ); var cell = newCellGo.GetComponent<UICell>();
cellCaches.Add(cell); newCellGo.gameObject.SetActiveEffectively(false);
}
}
}
}
作者:雨天
地址:http://www.cnblogs.com/HelloGalaxy/p/8697844.html
基于Unity·UGUI实现的RecycleList循环列表UI容器的更多相关文章
- (转载)基于Unity~UGUI的简单UI框架(附UIFramework源码)
此博客跟随siki老师的课程笔记生成,感谢siki老师的辛勤付出! 此框架功能较简单,适用于学习,可以很好的锻炼我们的设计思想 框架源码地址: UIFramework litjson.dll下载地址: ...
- Unity UGUI —— 无限循环List
还记得大学毕业刚工作的时候是做flash的开发,那时候看到别人写的各种各样的UI组件就非常佩服,后来自己也慢慢尝试着写,发现其实也就那么回事.UI的开发其实技术的成分相对来说不算多,但是一个好的UI是 ...
- Unity UGUI图文混排源码(二)
Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):ht ...
- Unity UGUI鼠标穿透UI问题(Unity官方的解决方法)
简述 最近在用UGUI的时候遇到了鼠标穿透的问题,就是说在UGUI和3D场景混合的情况下,点击UI区域同时也会 触发3D中物体的鼠标事件.比如下图中 这里给Cube加了一个鼠标点击改变颜色的代码,如下 ...
- Unity ugui屏幕适配与世界坐标到ugui屏幕坐标的转换
我们知道,如今的移动端设备分辨率五花八门,而开发过程中往往只取一种分辨率作为设计参考,例如采用1920*1080分辨率作为参考分辨率. 选定了一种参考分辨率后,美术设计人员就会固定以这样的分辨率来设计 ...
- Unity UGUI事件接口概述
UGUI 系统虽然提供了很多封装好的组件,但是要实现一些特定的功能还是显得非常有限,这时候就需要使用事件接口来完成UI功能的实现.比如我们想实现鼠标移动到图片上时自动显示图片的文字介绍,一般思路会想到 ...
- [Unity UGUI序列帧]简单实现序列帧的播放
在使用序列帧之前需要准备好序列帧的图集,打图集的操作参考 [Unity UGUI图集系统]浅谈UGUI图集使用 准备好序列帧图集,序列帧的播放原理就是获取到图集中的所有图片,然后按照设置的速度按个赋值 ...
- Unity-UGUI-无限循环列表
前言:项目准备新增一个竞技场排行榜,策划规定只显示0-400名的玩家.我一想,生成四百个游戏物体,怕不是得把手机给卡死?回想原来在GitHub上看到过一个实现思路就是无限循环列表,所以就想自己试试.公 ...
- 基于EasyUI Treegrid的权限管理资源列表
1. 前言 最近在开发系统权限管理相关的功能,主要包含用户管理,资源管理,角色管理,组类别管理等小的模块.之前的Web开发中也用过jQueryEasyUI插件,感觉这款插件简单易用,上手很快.以前用到 ...
随机推荐
- 【CF17E】Palisection(回文树)
[CF17E]Palisection(回文树) 题面 洛谷 题解 题意: 求有重叠部分的回文子串对的数量 所谓正难则反 求出所有不重叠的即可 求出以一个位置结束的回文串的数量 和以一个位置为开始的回文 ...
- [BZOJ1005] [HNOI2008] 明明的烦恼 (prufer编码)
Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树? Input 第一行为N ...
- Eclipse增强代码提示插件Code Recommenders安装,顺便说说Eclipse插件安装方法
1.为什么用Code Recommenders 在用过Intelij Idea后,发现它的自动代码提示非常智能,可以敲关键字就能提示,但是因为公司用的是Eclipse, 所以想找有没有这个插件能增强代 ...
- Eclipse Web项目配置
1.每次重开workspace都要重新配置一次 2.new web project之前配置 3.Windows-Preferences-(所有都要记得Apply) General Maven P. ...
- 【python学习笔记】8.异常
[python学习笔记]8.异常 raise Exception: 抛出指定异常 try/except: 捕捉异常 except: 第一个参数是需要捕获的异常类型,可以是多个类型组成元组,第二个参数是 ...
- Angular4---起步----环境配置安装@angular/cli
学习angular,首先要搭建起angular的手脚架@angular/cli.首先需要NodeJS环境. 1.安装NodeJS 首先检查电脑是否安装了NodeJS环境,打开cmd命令行,运行node ...
- READ TABLE 的用法
SORT ITAB BY '你想比较的列'. " 排序以增加二分查找的速度 READ TABLE itab with key 'itab中某列' = ‘目标列' BINARY SEARCH. ...
- mysql与emoji和特殊字符
从微信登陆已经是非常普遍的登陆方式了,在数据库设计时也应该考虑相关性.一般存储open_id.图标.昵称就够了.其中昵称要特殊注意,否则可能就像这样: mysql : 1366 Incorrect s ...
- 笔记:Spring Cloud Feign Ribbon 配置
由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 的客户端的方式来自定义各个服务客户端调用的参 ...
- vue技术解析二之组件通信
--------------------------动画效果----------------------------------- <transition name="router-f ...