还记得大学毕业刚工作的时候是做flash的开发,那时候看到别人写的各种各样的UI组件就非常佩服,后来自己也慢慢尝试着写,发现其实也就那么回事。UI的开发其实技术的成分相对来说不算多,但是一个好的UI是绝对少不了底层组件的支持的。我个人认为UI组件中相对比较复杂的就是List了,所以,这两天实现了一个UGUI的list,写了好几个版本,最终这个版本是相对比较好用的,在这我介绍一下大概思路,一是巩固一下知识做个记录,二是发扬一下分享精神。嘿嘿,大家多多赐教。

写List有两个重点是需要考虑的:

1.list中的item总数问题,刚打开的时候如果同时生成多个item会有卡顿的现象,50个,100个可能没问题但是1000个2000个就比较难搞了。

2.当list滑动时如何加载后面的item,一般的逻辑应该是这样的:每当滑动到红线的位置就生成后面一列的item,但是做过UI开发的人知道,这种方法做做demo可以,但是现实项目中基本没这么用的。如下图

怎么解决这两个问题呢?其实很简单,想一想就知道,其实对于list来说我们操作的是数据,而且我们最多只能看到scroll view里面的 (行数*列数 + 1*列数 )这个多个item,所以理论上来说只需要生成(行数*列数 + 1*列数 )个item就可以。

但是,还是不行!why?因为还是会出现“生成以后马上就要显示”的问题,理论跟现实是有差距的,显示中就算再快的机器再快的性能生成一个item的时间也不可能为0,所以如果一个item生成的时间和它被显示的时间重叠,在体验上肯定好不了。

所以我们还要再多生成3列,多出来的几列就相当于一个缓冲。

好吧,我直接说我的逻辑吧。

1.生成行数*列数+n*列数个item,这里n其实是一个可控变量,他的值从必须要大于等于3,这里我们给list的起始端定义了2个格子缓冲,末端最少要有1个格子的缓冲,这样才能保证拖动的时候看不到空白的部分。

2.根据数据的长度,来计算整个item显示区的rect,就是说在设置list数据的时候根据长度计算出整个滑动距离的最大值,即真正填满所有数据要显示的长度,这个主要是为了匹配Unity UGUI中的ScrollBar,因为ScrollBar的滑动边界是根据这个距离算出来的。

使用这个list的时候可以直接给ScrollRect指定一个ScrollBar,在滑动时就会自动适配位置。和UGUI原生的ScrollRect+ScrollBar使用方法一样。

3.每当item移出超过2个item的距离,就将移出的一列移动到最后并重新设置里面的数据。

OK话不多说,贴代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
/// <summary>
/// 无限循环List
/// 作者:EdisonLee
///
public class UILoop1 : UIBase
{ enum Direction
{
Horizontal,
Vertical
} [SerializeField]
private RectTransform m_Cell; [SerializeField]
private Vector2 m_Page; [SerializeField]
Direction direction = Direction.Horizontal; [SerializeField,Range(,)]
private int m_BufferNo; private List<RectTransform> m_InstantiateItems = new List<RectTransform>(); private IList m_Datas; public Vector2 CellRect { get { return m_Cell != null ? m_Cell.sizeDelta : new Vector2(, ); } } public float CellScale { get { return direction == Direction.Horizontal ? CellRect.x : CellRect.y; } } private float m_PrevPos = ;
public float DirectionPos { get { return direction == Direction.Horizontal ? m_Rect.anchoredPosition.x : m_Rect.anchoredPosition.y; } } private int m_CurrentIndex;//页面的第一行(列)在整个conten中的位置 private Vector2 m_InstantiateSize = Vector2.zero;
public Vector2 InstantiateSize
{
get
{
if (m_InstantiateSize == Vector2.zero)
{
float rows, cols;
if (direction == Direction.Horizontal)
{
rows = m_Page.x;
cols = m_Page.y + (float)m_BufferNo;
}
else
{
rows = m_Page.x + (float)m_BufferNo;
cols = m_Page.y;
}
m_InstantiateSize = new Vector2(rows, cols);
}
return m_InstantiateSize;
}
} public int PageCount { get { return (int)m_Page.x * (int)m_Page.y; } } public int PageScale { get { return direction == Direction.Horizontal ? (int)m_Page.x : (int)m_Page.y; } } private ScrollRect m_ScrollRect; private RectTransform m_Rect;
public int InstantiateCount { get { return (int)InstantiateSize.x * (int)InstantiateSize.y; } }
protected override void Awake()
{
m_ScrollRect = GetComponentInParent<ScrollRect>();
m_ScrollRect.horizontal = direction == Direction.Horizontal;
m_ScrollRect.vertical = direction == Direction.Vertical; m_Rect = GetComponent<RectTransform>(); m_Cell.gameObject.SetActive(false);
} public override void Data(object data)
{
m_Datas = data as IList; if (m_Datas.Count > PageCount)
{
setBound(getRectByNum(m_Datas.Count));
}
else
{
setBound(m_Page);
} if (m_Datas.Count > InstantiateCount)
{
while (m_InstantiateItems.Count < InstantiateCount)
{
createItem(m_InstantiateItems.Count);
}
}
else
{
while (m_InstantiateItems.Count > m_Datas.Count)
{
removeItem(m_InstantiateItems.Count - );
} while (m_InstantiateItems.Count < m_Datas.Count)
{
createItem(m_InstantiateItems.Count);
}
}
} private void createItem(int index)
{
RectTransform item = GameObject.Instantiate(m_Cell);
item.SetParent(transform, false);
item.anchorMax = Vector2.up;
item.anchorMin = Vector2.up;
item.pivot = Vector2.up;
item.name = "item" + index; item.anchoredPosition = direction == Direction.Horizontal ?
new Vector2(Mathf.Floor(index / InstantiateSize.x) * CellRect.x, -(index % InstantiateSize.x) * CellRect.y) :
new Vector2((index % InstantiateSize.y) * CellRect.x, -Mathf.Floor(index / InstantiateSize.y) * CellRect.y);
m_InstantiateItems.Add(item);
item.gameObject.SetActive(true); updateItem(index, item.gameObject);
} private void removeItem(int index)
{
RectTransform item = m_InstantiateItems[index];
m_InstantiateItems.Remove(item);
RectTransform.Destroy(item.gameObject);
}
/// <summary>
/// 由格子数量获取多少行多少列
/// </summary>
/// <param name="num"></param>格子个数
/// <returns></returns>
private Vector2 getRectByNum(int num)
{
return direction == Direction.Horizontal ?
new Vector2(m_Page.x, Mathf.CeilToInt(num / m_Page.x)) :
new Vector2(Mathf.CeilToInt(num / m_Page.y), m_Page.y); }
/// <summary>
/// 设置content的大小
/// </summary>
/// <param name="rows"></param>行数
/// <param name="cols"></param>列数
private void setBound(Vector2 bound)
{
m_Rect.sizeDelta = new Vector2(bound.y * CellRect.x, bound.x * CellRect.y);
} public float MaxPrevPos
{
get
{
float result;
Vector2 max = getRectByNum(m_Datas.Count);
if(direction == Direction.Horizontal)
{
result = max.y - m_Page.y;
}
else
{
result = max.x - m_Page.x;
}
return result * CellScale;
}
}
public float scale { get { return direction == Direction.Horizontal ? 1f : -1f; } }
void Update()
{
while (scale * DirectionPos - m_PrevPos < -CellScale * )
{
if (m_PrevPos <= -MaxPrevPos) return; m_PrevPos -= CellScale; List<RectTransform> range = m_InstantiateItems.GetRange(, PageScale);
m_InstantiateItems.RemoveRange(, PageScale);
m_InstantiateItems.AddRange(range);
for (int i = ; i < range.Count; i++)
{
moveItemToIndex(m_CurrentIndex * PageScale + m_InstantiateItems.Count + i, range[i]);
}
m_CurrentIndex++;
} while (scale * DirectionPos - m_PrevPos > -CellScale)
{
if (Mathf.RoundToInt(m_PrevPos) >= ) return; m_PrevPos += CellScale; m_CurrentIndex--; if (m_CurrentIndex < ) return; List<RectTransform> range = m_InstantiateItems.GetRange(m_InstantiateItems.Count - PageScale, PageScale);
m_InstantiateItems.RemoveRange(m_InstantiateItems.Count - PageScale, PageScale);
m_InstantiateItems.InsertRange(, range);
for (int i = ; i < range.Count; i++)
{
moveItemToIndex(m_CurrentIndex * PageScale + i, range[i]);
}
}
} private void moveItemToIndex(int index, RectTransform item)
{
item.anchoredPosition = getPosByIndex(index);
updateItem(index, item.gameObject);
} private Vector2 getPosByIndex(int index)
{
float x, y;
if(direction == Direction.Horizontal)
{
x = index % m_Page.x;
y = Mathf.FloorToInt(index / m_Page.x);
}
else
{
x = Mathf.FloorToInt(index / m_Page.y);
y = index % m_Page.y;
} return new Vector2(y * CellRect.x, -x * CellRect.y);
} private void updateItem(int index, GameObject item)
{
item.SetActive(index < m_Datas.Count); if(item.activeSelf)
{
UILoopItem lit = item.GetComponent<UILoopItem>();
lit.UpdateItem(index, item);
lit.Data(m_Datas[index]);
}
}
}

经测试3-4千个数据还是跑的刚刚的流畅,再往上就没试过了,其实数据个数是根据设备性能而定的,因为显示的Item只有固定的那几个,所以性能的损耗就是在使用list之前设置数据生成数据的时候,也就是说跟list无关了,跟设备的内存,cpu有关。

本文固定链接:http://www.cnblogs.com/fly-100/p/4549354.html

转载请注明出处,请尊重原创,多谢!

Unity UGUI —— 无限循环List的更多相关文章

  1. Unity UGUI —— 无限循环List(转载)

    using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; ...

  2. unity 背景无限循环滚动效果

    背景无限循环滚动效果如下示: 步骤如下: 导入背景图片后,设置图片的格式,如下图: 2.图片格式也可以设置是Texture格式,但是Wrap Mode 一定要是Repeat[重复发生]:然后记得App ...

  3. [Unity UGUI]ScrollRect效果大全

    UGUI各种优化效果 本文所实现的UGUI效果需求如下: - 支持缩放滑动效果 - 支持动态缩放循环加载 - 支持大数据固定Item复用加载 - 支持不用Mask遮罩无限循环加载 - 支持Object ...

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

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

  5. 详细分析Android viewpager 无限循环滚动图片

    由于最近在忙于项目,就没时间更新博客了,于是趁着周日在房间把最近的在项目中遇到的技术总结下.最近在项目中要做一个在viewpager无限滚动图片的需求,其实百度一下有好多的例子,但是大部分虽然实现了, ...

  6. 一行代码引入 ViewPager 无限循环 + 页码显示

    (出处:http://www.cnblogs.com/linguanh) 前序: 网上的这类 ViewPager 很多,但是很多都不够好,体现在 bug多.对少页面不支持,例如1~2张图片.功能整合不 ...

  7. iOS开发系列--无限循环的图片浏览器

    --UIKit之UIScrollView 概述 UIKit框架中有大量的控件供开发者使用,在iOS开发中不仅可以直接使用这些控件还可以在这些控件的基础上进行扩展打造自己的控件.在这个系列中如果每个控件 ...

  8. 使用 iscroll 实现焦点图无限循环

    现在大家应该都看到过焦点图轮播的效果,这个效果是什么样我就不截图了.昨天做练习,练习要求是使用iscroll实现焦点图的无限循环滚动,并且当手指触摸焦点图后,停止焦点图的循环滚动.第一次接触iscro ...

  9. iOS无限循环滚动scrollview

    经常有园友会问"博主,有没有图片无限滚动的Demo呀?", 正儿八经的图片滚动的Demo我这儿还真没有,今天呢就封装一个可以在项目中直接使用的图片轮播.没看过其他iOS图片无限轮播 ...

随机推荐

  1. 自定义View常处理的回调函数

    自定义View常处理的回调函数 onFinishInflate() 当View中所有的子控件均被映射成xml后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(bo ...

  2. HDU 1815 Building roads

    二分答案 + 2-SAT验证 POJ 稳过,HDU C++ 超时,G++ 550ms左右AC #include<cstdio> #include<cstring> #inclu ...

  3. tinkphp5.0 traits 的引入

    Traits引入 ThinkPHP 5.0开始采用trait功能(PHP5.4+)来作为一种扩展机制,可以方便的实现一个类库的多继承问题. trait是一种为类似 PHP 的单继承语言而准备的代码复用 ...

  4. MC34063中文资料及应用实例(转)

    源:http://blog.chinaunix.net/uid-26199686-id-3207838.html MC34063A(MC33063)芯片器件简介 该器件本身包含了DC/DC变换器所需要 ...

  5. CSS的继承性和层叠性

    1.继承性 比不是所有的属性都能继承,只有一些文本的属性才能被继承.比如:color,text-开头的,line-开头的,font-开头的.这几种属性才能被继承. 其余的关于盒子,定位,布局的都不能被 ...

  6. 在MAC下配置MySQL 5.7 数据库的编码问题

    1.MySQL 5.7 for MAC 默认没有my.cnf文件 ,首先 新建my.cnf文件: 2.在my.cnf文件追加 [mysqld] character-set-server=utf8mb4 ...

  7. Quick Cocos2dx Http通讯

    服务端:Python 通讯协议:Http 参考文章: 1 用python实现一个基本的http server服务器 http://blog.sina.com.cn/s/blog_416e3063010 ...

  8. [Unity Physics] Physics - Raycast

    Class Variables类变量 gravity The gravity applied to all rigid bodies in the scene.场景中应用到所有刚性物体的重力. min ...

  9. C# MODBUS协议 上位机(转)

    源:C# MODBUS协议 上位机 C#写了一款上位机监控软件,基于MODBUS_RTU协议. 软件的基本结构: 采用定时器(Timer控件)为时间片. 串口采用serialPort1_DataRec ...

  10. Eclipse 的 git 插件操作 "代码提交"以及"代码冲突"

    面向对象:曾经使用过SVN的同学. (因为Git 它 可以说是双重的SVN (本地一个服务器,远程一个服务器)),提交代码要有两次步骤,先提交到本地服务器,再把本地服务器在提交到远程服务器. 所以连S ...