unity中ScrollRect虚拟化列表的简单实现

        ScrollRect算是项目中需要经常用到的一个组件。但是因为unity本身并不支持scrollrect的虚拟化显示。在数据量很大时scrollrect中就会创建了过多的子项,导致项目卡顿。所以抽空简单实现了下项的重复使用,以达到性能优化的目的。

        首先,先在界面上放一个ScrollRect,建立一个用于复制的子项:

         因为当前并不处理横向滚动,故先将横向的Scrollbar干点了。

        然后在控件上绑定自己写的类,在Start中动态生成一定数量的项用于动态显示:

        wawawa

void Start()
    {
        //这个类需要和ScrollRect组件绑在一起,如果没有,请重新对ScrollRect赋值
        ScrollRect = GetComponent<ScrollRect>();
        //将Content固定为顶对齐
        ScrollRect.content.anchorMin = new Vector2(0,1);
        ScrollRect.content.anchorMax = new Vector2(1, 1);
        //y轴固定为0,方便计算
        ScrollRect.content.anchoredPosition = new Vector2(ScrollRect.content.anchoredPosition.x,0);


        //先将单个项的中心点移到顶部
        Vector2 pivot = ItemBase.pivot;
        pivot.y = 1;
        ItemBase.pivot = pivot;

        //将项修改为顶部对齐方式
        ItemBase.anchorMin = new Vector2(0.5f, 1);
        ItemBase.anchorMax = new Vector2(.5f, 1);

        itemHeight = ItemBase.sizeDelta.y;

        //计算需要创建多少个项用于重复显示
        //使用ScrollRect的高度,才能正确反应可视区域的高度
        float height = ScrollRect.GetComponent<RectTransform>().sizeDelta.y;
        height -= TopMagin + BottomMagin;
        int itemCount = (int)Mathf.Ceil(height / (itemHeight + ItemMagin));
        //多创建两个,避免上下滑动时出现穿帮
        itemCount += 2;

        ItemBase.gameObject.SetActive(false);
        for (int i = 0; i < itemCount; i++)
        {
            GameObject item = GameObject.Instantiate(ItemBase.gameObject);
            item.transform.SetParent(ScrollRect.content);
            Items.Add(item.GetComponent<RectTransform>());
            Items[i].anchoredPosition = Vector2.zero;
        }
        //监听 onValueChanged事件,以便对Items进行数据和坐标的更新 
        ScrollRect.onValueChanged.AddListener((Vector2 vec2)=> {
            ResetItemInfo();
        });
    }

        里面用到的一些局部属性定义:

         


    private ScrollRect ScrollRect;

    /// <summary>
    /// 用于循环的列表项
    /// </summary>
    public RectTransform ItemBase;
    /// <summary>
    /// 每个项的间距
    /// </summary>
    public float ItemMagin;
    /// <summary>
    /// 第一个项和顶部的距离
    /// </summary>
    public float TopMagin = 0;
    /// <summary>
    /// 最后一个项同底部的距离
    /// </summary>
    public float BottomMagin = 0;
    /// <summary>
    /// 回调,用于刷新单个项的数据,第一个参数为项实例,第二个参数为数据下标
    /// </summary>
    public Action<RectTransform, int> OnItemChanged;
    /// <summary>
    /// 单个项的高度
    /// </summary>
    private float itemHeight = 0;


    private int DataCount;

    private List<RectTransform> Items = new List<RectTransform>();

然后就是公布一个外部可调用的接口方法。这个类本身并不需要关注数据的具体信息,所以只需要传入数据长度用于计算scrollrect的content高度就好了:

public void Refresh(int dataCount)
    {
        DataCount = dataCount;
        //先计算出总的高度
        float height = itemHeight * DataCount + (DataCount - 1) * ItemMagin + TopMagin + BottomMagin;
        //设置出content新的高度
        ScrollRect.content.sizeDelta = new Vector2(ScrollRect.content.sizeDelta.x,height);

        ResetItemInfo();
    }

最重要的部分当然是对循环项的重新赋值和坐标定位了啊:

private void ResetItemInfo()
    {
        float contentY = ScrollRect.content.anchoredPosition.y;
        contentY -= TopMagin;
        //当前可显示的数据下标
        int index = (int)Mathf.Floor(contentY / (itemHeight + ItemMagin));
        index = index < 0 ? 0 : index;

        float startY = TopMagin + index * itemHeight + ItemMagin * (index - 1);
        startY = -startY;
        int itemIndex = 0;
        for (int i = index; i < DataCount; i++)
        {
            if (Items.Count <= itemIndex)
            {
                break;
            }
            if (!Items[itemIndex].gameObject.activeSelf)
            {
                Items[itemIndex].gameObject.SetActive(true);
            }
            Items[itemIndex].anchoredPosition = new Vector2(Items[itemIndex].anchoredPosition.x,startY);
            //回调,外部绑定OnItemChange以便对项的显示进行刷新
            OnItemChanged?.Invoke(Items[itemIndex],i);
            startY -= itemHeight + ItemMagin;
            itemIndex++;
        }

        //多余的项隐藏
        for (int i = itemIndex; i < Items.Count; i++)
        {
            if (Items[i].gameObject.activeSelf)
            {
                Items[i].gameObject.SetActive(false);
            }
        }

    }

具体使用的时候就只需要刷新数据长度和绑定OnItemChanged事件回调进行数据刷新就好啦。

这是测试代码:先根据输入的数字创建指定长度的数据。然后在回调中处理更新单个项的显示信息:

 int num = 0;
        if(int.TryParse(InputField.text,out num))
        {
            if (num > 0)
            {
                int index = datas.Count;
                for (int i = 0; i < num; i++)
                {
                    datas.Add(new Data() { 
                        name="Item"+(index+i)
                    });
                }
                VirtualScroll.Refresh(datas.Count);
            }
        }
        VirtualScroll.OnItemChanged = (RectTransform item, int index) => {
            item.GetComponentInChildren<Text>().text = datas[index].name;
        };

详细的例子可以看我上传的资源:https://download.csdn.net/download/lbfht/87300730

猜你喜欢

转载自blog.csdn.net/lbfht/article/details/128342099
今日推荐