在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容器的更多相关文章

  1. (转载)基于Unity~UGUI的简单UI框架(附UIFramework源码)

    此博客跟随siki老师的课程笔记生成,感谢siki老师的辛勤付出! 此框架功能较简单,适用于学习,可以很好的锻炼我们的设计思想 框架源码地址: UIFramework litjson.dll下载地址: ...

  2. Unity UGUI —— 无限循环List

    还记得大学毕业刚工作的时候是做flash的开发,那时候看到别人写的各种各样的UI组件就非常佩服,后来自己也慢慢尝试着写,发现其实也就那么回事.UI的开发其实技术的成分相对来说不算多,但是一个好的UI是 ...

  3. Unity UGUI图文混排源码(二)

    Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):ht ...

  4. Unity UGUI鼠标穿透UI问题(Unity官方的解决方法)

    简述 最近在用UGUI的时候遇到了鼠标穿透的问题,就是说在UGUI和3D场景混合的情况下,点击UI区域同时也会 触发3D中物体的鼠标事件.比如下图中 这里给Cube加了一个鼠标点击改变颜色的代码,如下 ...

  5. Unity ugui屏幕适配与世界坐标到ugui屏幕坐标的转换

    我们知道,如今的移动端设备分辨率五花八门,而开发过程中往往只取一种分辨率作为设计参考,例如采用1920*1080分辨率作为参考分辨率. 选定了一种参考分辨率后,美术设计人员就会固定以这样的分辨率来设计 ...

  6. Unity UGUI事件接口概述

    UGUI 系统虽然提供了很多封装好的组件,但是要实现一些特定的功能还是显得非常有限,这时候就需要使用事件接口来完成UI功能的实现.比如我们想实现鼠标移动到图片上时自动显示图片的文字介绍,一般思路会想到 ...

  7. [Unity UGUI序列帧]简单实现序列帧的播放

    在使用序列帧之前需要准备好序列帧的图集,打图集的操作参考 [Unity UGUI图集系统]浅谈UGUI图集使用 准备好序列帧图集,序列帧的播放原理就是获取到图集中的所有图片,然后按照设置的速度按个赋值 ...

  8. Unity-UGUI-无限循环列表

    前言:项目准备新增一个竞技场排行榜,策划规定只显示0-400名的玩家.我一想,生成四百个游戏物体,怕不是得把手机给卡死?回想原来在GitHub上看到过一个实现思路就是无限循环列表,所以就想自己试试.公 ...

  9. 基于EasyUI Treegrid的权限管理资源列表

    1. 前言 最近在开发系统权限管理相关的功能,主要包含用户管理,资源管理,角色管理,组类别管理等小的模块.之前的Web开发中也用过jQueryEasyUI插件,感觉这款插件简单易用,上手很快.以前用到 ...

随机推荐

  1. [NOIP2017] 逛公园

    [NOIP2017] 逛公园 题目大意: 给定一张图,询问长度 不超过1到n的最短路长度加k 的1到n的路径 有多少条. 数据范围: 点数\(n \le 10^5\) ,边数\(m \le 2*10^ ...

  2. javascript三角函数的使用

    其实很多编程语言里面都有数学函数,而且很多数学函数包括三角函数,只不过有些时候可能我们用的并不多,我最近在做一个h5的游戏,其中有一个需求就是射击的枪支需要更随鼠标变换位置,鼠标移动到什么地方,炮口就 ...

  3. 重磅消息-Service Fabric 正式开源

    微软的Azure Service Fabric的官方博客在2017.3.24日发布了一篇博客 Service Fabric .NET SDK goes open source ,介绍了社区呼声最高的S ...

  4. ES6学习总结一(变量;箭头函数;解构赋值)

    一.变量 var  1 可以重复声明(var a=1;var a=7;)(一开始用着会觉得限制很少,但是在大型项目会麻烦,人多嘴杂的时候定义重复了就容易出问题还不好找) 2 无法限制修改 3 没有块级 ...

  5. log4j日志输出性能优化-缓存、异步

     转载 1.log4j已成为大型系统必不可少的一部分,log4j可以很方便的帮助我们在程序的任何位置输出所要打印的信息,便于我们对系统在调试阶段和正式运行阶段对问题分析和定位.由于日志级别的不同,对系 ...

  6. oracle使用中的一些问题

    一.设置自增主键(假设表的主键名为:company_id) 1)创建序列(company_autoinc): maxvalue start increment nocache; 2)创建触发器(com ...

  7. HandsonTable日期控件的汉化

    由于项目的需要,想把HandsonTable自带的日期中英文替换成中文.其实这个不难,只要在库文件中替换下就可以了,替换的效果对比如下:         如果有需要的同学,可以在此处下载 By QJL

  8. handsontable 事件汇总

    Hook插件 afterChange (changes: Array, source: String):1个或多个单元格的值被改变后调用 changes:是一个2维数组包含row,prop,oldVa ...

  9. JavaScript编码规范(2)

    变量 [强制] 变量.函数在使用前必须先定义. // good var name = 'MyName'; // bad name = 'MyName'; [强制] 每个 var 只能声明一个变量. 解 ...

  10. Spring Security 源码解析(一)AbstractAuthenticationProcessingFilter

    # 前言 最近在做 Spring OAuth2 登录,并在登录之后保存 Cookies.具体而言就是 Spring OAuth2 和 Spring Security 集成.Google一下竟然没有发现 ...