UI的开发其实技术的成分相对来说不算多,但是一个好的UI是绝对少不了底层组件的支持的。我个人认为UI组件中相对比较复杂的就是List了,所以,这两天实现了一个UGUI的list,写了好几个版本,最终这个版本是相对比较好用的,在这我介绍一下大概思路,一是巩固一下知识做个记录,二是发扬一下分享精神。嘿嘿,大家多多赐教。

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

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

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


unity 无限滚动列表原理 unity无限循环列表_List

 

怎么解决这两个问题呢?其实很简单,想一想就知道,其实对于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(4,10)]        
         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(100, 100); } }        
                  
         public          float          CellScale {          get          {          return          direction == Direction.Horizontal ? CellRect.x : CellRect.y; } }        
                  
         private          float          m_PrevPos = 0;        
         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 - 1);        
         }        
                  
         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 * 2)        
         {        
         if          (m_PrevPos <= -MaxPrevPos)          return         ;        
                  
         m_PrevPos -= CellScale;        
                  
         List<RectTransform> range = m_InstantiateItems.GetRange(0, PageScale);        
         m_InstantiateItems.RemoveRange(0, PageScale);        
         m_InstantiateItems.AddRange(range);        
         for          (         int          i = 0; 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) >= 0)          return         ;        
                  
         m_PrevPos += CellScale;        
                  
         m_CurrentIndex--;        
                  
         if          (m_CurrentIndex < 0)          return         ;        
                  
         List<RectTransform> range = m_InstantiateItems.GetRange(m_InstantiateItems.Count - PageScale, PageScale);        
         m_InstantiateItems.RemoveRange(m_InstantiateItems.Count - PageScale, PageScale);        
         m_InstantiateItems.InsertRange(0, range);        
         for          (         int          i = 0; 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有关。好了,本篇unity3d教程关于用ugui制作无限循环list列表的介绍就到此结束,下篇我们再会!