Unity UGUI —— 无限循环List

还记得大学毕业刚工作的时候是做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(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有关。

时间: 2024-10-20 15:57:25

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

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

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 Rec

unity 背景无限循环滚动效果

背景无限循环滚动效果如下示: 步骤如下: 导入背景图片后,设置图片的格式,如下图: 2.图片格式也可以设置是Texture格式,但是Wrap Mode 一定要是Repeat[重复发生]:然后记得Apply一下.[解释:Wrap mode :循环模式:换行模式:包裹模式:缠绕] 3.在Hierachy视图中,新建2D Object-〉Sprite 或者一个Image,将之前设置好的精灵放入Texture. 4.在Project视图鼠标右键新建一个Shader,命名随意,然后在编辑器中修改Shade

renderer_outline_aa::line3()无限循环

情况一: 在该函数出现无限循环在如下两句代码: while(abs(sx - lp.x1) + abs(sy - lp.y1) > lp2.len){sx = (lp.x1 + sx) >> 1;sy = (lp.y1 + sy) >> 1;} 断点调试的时候,出现无限循环的结果是因为取值出现如下的问题: sx=0x3d00lp.x1=0x3d01sy=0x7a00lp.y1=0x7a01lp2.len=1 在这种情况下,sx和sy的值永远不变,并且循环的条件永远成立(whi

自定义 ViewGroup 支持无限循环翻页之三(响应回调事件)

大家如果喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处,再次感谢 ####################################################################### 自定义 ViewGroup 支持无限循环翻页系列 自定义 ViewGroup 支持无限循环翻页之一(重写 onLayout以及 dispatchDraw) 自定义 ViewGroup 支持无限循环翻页之二(处理触摸事件)

Ubuntu中输入startx并重启后登录界面无限循环问题

今天,在学习了编程两个多月后遇到的第一个菜鸟技术问题,解决后有一种兴奋和迷茫. 言归正传,说说遇到的问题和解决方案. 在Ubuntu14.04中,按照linux的传统做法(基于redhat),在root下输入了startx并enter,结果出现了一个没有任何图标的界面,没有任何命令可以输入,于是选择重启,那么问题来了. 重启后进入登录界面,但是正确输入密码后还是输入密码的无限循环,纠结...Google.百度后发现原来有好多菜鸟也有同样的问题,于是对此问题进行了探讨学习. 先说解决方案: (1)

(原创)c#学习笔记04--流程控制04--循环04--循环中断和无限循环

4.4.4  循环的中断 有时需要更精细地控制循环代码的处理.C#为此提供了4个命令,其中的3个已经在其他情形中介绍过了:?  break——立即终止循环.?  continue——立即终止当前的循环(继续执行下一次循环).?  goto——可以跳出循环,到已标记好的位置上(如果希望代码易于阅读和理解,最好不要使用该命令).?  return——跳出循环及其包含的函数(参见第6章).?  break命令可退出循环,继续执行循环后面的第一行代码 4.4.5  无限循环 可以通过编写错误代码或错误的

UIScrollView实现图片轮播器及其无限循环效果

图片轮播器: 一.实现效果 实现图片的自动轮播            二.实现代码 storyboard中布局 代码: 1 #import "YYViewController.h" 2 3 @interface YYViewController () <UIScrollViewDelegate> 4 @property (weak, nonatomic) IBOutlet UIScrollView *scrollview; 5 /** 6 * 页码 7 */ 8 @prop

【Android】ViewPager实现无限循环滚动

最近做的一个项目,客户要求在ViewPager实现的主页面中滑动到最后一页后继续滑动能返回到第一页,也就是实现无限循环滚动,效果如下: 看了下ViewPager没有滑到尽头的回调方法,因此想到的解决方案是,在原来的最后一页之后再加上一个第一页,也就是原本有编号为a1,b,c的三个页面,现在在最后面再加一个a页面,变为a1.b.c.a2四个页面,然后使用OnPageChangeListener中的onPageSelected方法来监听到页面切换,当发现是从第三个页面(c)切换到第四个页面(a1)时

viewpager 自动无限循环 这段代码移到你的程序中就可以用了

本程序需要懂得viewpager的基础知识,也就是说你能够自己写出一个手动滑动的viewpager,下面我将附带加小圆点的知识 那么我们回顾一下图片轮转的基础知识,如果你已经对viewpager很熟悉了就不用看了,浪费时间 1.viewpager是在v4兼容报里面的,使用控件时请带上报名 2.数据来源用list存放,我这里用的是textview做的小圆点list<textview>,layout做的单张幻灯片list<view>,将xml文件inflate成为view,View.i