基于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插件,感觉这款插件简单易用,上手很快.以前用到 ...
随机推荐
- [BZOJ1926][SDOI2010]粟粟的书架
BZOJ Luogu Description 幸福幼儿园 B29 班的粟粟是一个聪明机灵.乖巧可爱的小朋友,她的爱好是画画和读书,尤其喜欢 Thomas H. Cormen 的文章.粟粟家中有一个 R ...
- webstorm修改工具栏主题
** 操作如图所示 **
- UML那些事
什么是UML?它的全名:Unified Modeling Language,统一建模语言.最近我用到了uml,顺便重温了下这些知识.知乎上有一个讨论话题:uml还有用吗?这个讨论挺有意思的,看完后,受 ...
- Android OpenGL ES 开发(十): 通过GLES20与着色器交互
1. 获取着色器程序内成员变量的id(句柄.指针) GLES20.glGetAttribLocation方法:获取着色器程序中,指定为attribute类型变量的id. GLES20.glGetUni ...
- 关于Web Audio API的入门
Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...
- Python基本知识
python是一门编程语言,这是我们学习python首先要了解的内容,那么编程语言又是什么,我们为什么需要编程语言 我们讲的话是汉语,英语或者其他语言,计算机也可以讲话,但是他只会说0,1,也只能看懂 ...
- Django数据模型——数据库字段类型
字段 一个模型最重要也是唯一必需的部分,是它定义的数据库字段 字段名称限制 1.字段名不能是python保留字,这样会导致python语法错误 2.字段不能包含连续一个以上的下划线,这样会和Djang ...
- JDK1.8源码(三)——java.lang.String 类
String 类也是java.lang 包下的一个类,算是日常编码中最常用的一个类了,那么本篇博客就来详细的介绍 String 类. 1.String 类的定义 public final class ...
- IPFS家族(一)
IPFS这个项目其实很大,并不像大家想象的是一个东西,IPFS是由很多模块组成,每一个模块现在都已经独立成项目了,并且有自己的主页.让我们来简单看一下IPFS家族成员. 协议实验室的主页:https: ...
- VMware静态地址上网
虚拟机通过dhcp获取ip,当系统重启时可能导致ip变更,出现不必要的麻烦,以下是通过nat模式设置虚拟机静态ip同时能够上网的方式. 编辑VMware,依次点击“编辑”--“虚拟网络编辑器” 注:为 ...