基于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插件,感觉这款插件简单易用,上手很快.以前用到 ...
随机推荐
- 关于js的语句类型运算符等
JavaScript的基本语句有以下几种: 1.for(判断条件){执行内容}: 2.if(条件){...}else{ ....}: 3.while(条件){....}:先判断条件再执行: do{.. ...
- 关于flex的布局理解
flex布局是一种新的布局方式也就是弹性盒子:在布局上更加方便,但是要注意的是在flex布局中float.position是无效的. 图片来自阮一峰的博客 .下面我们进入正题: flex布局分为分为主 ...
- shell 脚本下执行Mongodb命令
最近项目中搭建了两台mongodb的服务器,由于服务器只有两台的情况下,目前只是搭建了主从模式架构(官方目前并不推荐主从模式),缺点就是故障转移不变等等原因,而是推荐副本集模式(这里就不多说了)... ...
- 宿主机无法访问linux虚机中的网站
问题现象: Nginx服务已启动 80端口被nginx监听 宿主和linux虚机可相互ping通 Linux虚机可用curl访问网站 宿主无法用浏览器访问网站 排查: 1. 查看nginx的acce ...
- Redis之List
一.Redis之List简介 1. List是简单的字符串列表,按照插入顺序排列. 2. 一个列表最多可存储232-1个元素(40多亿). 二.Redis之List命令行操作 Lrange:获取列表指 ...
- Apache Shiro 标签方式授权
Shiro提供了一套JSP标签库来实现页面级的授权控制. 在使用Shiro标签库前,首先需要在JSP引入shiro标签: <%@ taglib prefix="shiro" ...
- object转字符串
1.obj.tostring() obj为空时,抛异常. 2.convert.tostring(obj) obj为空时,返回null: 3.(string)obj obj为空时,返回null:obj不 ...
- python web开发-flask中response,cookies,session对象使用详解
Response响应对象: 当一个web请求被服务器处理完后,会返回用户请求的响应,这时候就要用到响应对象,根据响应给用户的形式不同,响应对象有以下几种处理方式 如果返回的是一个合法的响应对象,它会从 ...
- 理解C语言中几个常见修饰符
写在前面 今天下午一个同事问「register」关键字是什么作用?噢,你说的是「register」啊,它的作用是……脑袋突然断片儿,我擦,啥意思来着,这么熟悉的陌生感.做C语言开发时间也不短了,不过好 ...
- IPFS: BitSwap协议(数据块交换)
原创 2018-01-11 飞向未来 IPFS指南 BitSwap协议 IPFS节点之间是如何进行数据交换的?本文来讲一下这个问题. IPFS在BitTorrent的基础上实现了p2p数据交换协议:B ...