Unity 格子布局复用item滚动列表组件

之前先初步实现了功能,后来抽象出了一个父类,继承父类实现接口。

使用者只需要场景里拼好UI,然后自定义一个负责显示的类继承指定的基类,(数据类肯定是早就在其他模块就已经实现好了的),然后实例化LoopList对象调用Init方法即可满足需求。

不足:
1.格子都是大小一样的。
2.目前只完成了垂直方向的功能。
3.代码应该可以更简化(之前参考的一些代码实现该需求不到200行代码)

实现功能:

  1. 自动控制布局(格子型的布局控制,类似背包,要求每一个格子大小一样)
  2. 复用item Go对象,用面板里可见数量的UI对象 显示任意多个数据项。

效果图:
滚动过程中 8个item 显示了30个数据
Unity 格子布局复用item滚动列表组件_第1张图片

使用方式:

如下,设置好rect挂载Scroll Rect组件,指定content,content为rect的子物体,目前只实现了垂直方向的功能,如需水平方向,可自行实现。
Unity 格子布局复用item滚动列表组件_第2张图片
item设置:
注意锚点和中心点,该go为最终布局在content中的格子,锚点和中心点的设置也可用代码设置
Unity 格子布局复用item滚动列表组件_第3张图片
content设置:
同item设置
Unity 格子布局复用item滚动列表组件_第4张图片
自定义数据类,作为最终每个itemGo显示的内容
Unity 格子布局复用item滚动列表组件_第5张图片
自定义view类:
继承一个泛型父类,并将泛型参数设置为自定义的数据类
Unity 格子布局复用item滚动列表组件_第6张图片
测试代码,挂载于任意Go上即可。
Unity 格子布局复用item滚动列表组件_第7张图片
最后的核心类登场!

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

public class ICustomGridItem<T> where T : class
{
    protected T info;
    protected GameObject go;
    

    public virtual void Init(GameObject go)
    {
        this.go = go;
    }

    public virtual void UpdateView()
    {

    }
    public void SetInfo(T info)
    {
        this.info = info;
        UpdateView();
    }

}

public class MyLoopList<DataType, ItemType> where ItemType : ICustomGridItem<DataType>, new() where DataType:class 
{
    RectTransform scrollRect;

    RectTransform contentTR;
    public GameObject ItemGo;
    
    public int GridHeight;          //
    public int GridWidth;
    public int wholeItemCount;     //格子总数

    public int lineCount;           //每一行的格子数
    public Vector2 space;

    int line;                       //面板里的行数
    float contentHeight;            //content根据grid数量初始化后的高度

    float lastContentRTy;

    int itemCountInView;            //面板里可容纳的最大Item数量
    float scrollViewHeight;         //面板的初始高度

    int firstLineIndex;             //当前面板中的第一个Item的index

    LinkedList<RectTransform> itemList = new LinkedList<RectTransform>(); 
    int itemDataIndex;

    Dictionary<GameObject, ICustomGridItem<DataType>> gridList = new Dictionary<GameObject, ICustomGridItem<DataType>>();

    DataType[] datas;

    public void Init(DataType[] datas, Vector2 space, int gridHeight,
    int gridWidth, int lineCount, int wholeItemCount, GameObject itemGo, RectTransform scrollRect)
    {
        this.scrollRect = scrollRect;

        this.datas = datas;
        GridHeight = gridHeight;
        GridWidth = gridWidth;
        this.lineCount = lineCount;
        this.wholeItemCount = wholeItemCount;
        this.space = space;
        ItemGo = itemGo;
        
        contentTR = scrollRect.Find("content").GetComponent<RectTransform>();
        if (contentTR == null)
        {
            Debug.LogError("应该有叫'content'的子物体");
            return;
        }
        
        var rect = scrollRect.GetComponent<RectTransform>().rect;
        scrollViewHeight = rect.height;
        Vector2 size = Vector2.zero;
        size.x = rect.width;
        contentHeight = (space.y + GridHeight) * wholeItemCount / lineCount; //设置content的高度
        size.y = contentHeight;

        contentTR.sizeDelta = size; //content的总大小被设置
        //上一帧的 content y轴位置
        lastContentRTy = GetContentPositionY();

       
        //将预设的宽高设置
        (ItemGo.transform as RectTransform).sizeDelta = new Vector2(GridWidth, GridHeight);
        
        //计算面板里能显示多少行  +2 防止穿帮
        line = (int)(scrollViewHeight /(GridHeight+space.x)) + 2;

        itemCountInView = line * lineCount; // view窗口里显示的item数量
        for (int i = 1; i < itemCountInView + 1; i++)
        {
            GameObject go = GameObject.Instantiate(ItemGo, contentTR);
            var item = new ItemType();
            item.Init(go);
            gridList.Add(go,item );

            Vector3 pos = Vector3.zero;

            int count = (i % lineCount);
            if (count == 0) count = lineCount;

            pos.x = (i - 1) % lineCount * GridWidth + count * space.x;
            int hang = (int)(Mathf.Ceil(i * 1.0f / lineCount));

            pos.y = -((hang - 1) * GridHeight + hang * space.y);
            pos.z = 0;
            var rectt = (go.transform as RectTransform);
            rectt.anchoredPosition = pos;
            itemList.AddLast(rectt);
            
        }
        //第一个go的索引  最后一个go的索引(对应的数据索引)
        firstLineIndex = 0;
       
        scrollRect.GetComponent<ScrollRect>().onValueChanged.AddListener(OnValueChanged);

        ItemGo.SetActive(false);

        Refresh();
    }

    
    public void OnValueChanged(Vector2 pos)
    {
        float nowContentRTy = GetContentPositionY();
        //往上移
        bool isUp = false;

        if (nowContentRTy - lastContentRTy > 0)
        {
            if (scrollViewHeight + GetItemPositionY(false) + nowContentRTy > GridHeight
            && contentHeight - nowContentRTy > scrollViewHeight)
            {
                isUp = true;
                MoveItemGo(isUp);
                firstLineIndex += lineCount;
            }

        }
        else
        {
            if (-GetItemPositionY(true) - nowContentRTy > space.y
            && nowContentRTy > 0)
            {
                isUp = false;
                MoveItemGo(isUp);
                firstLineIndex -= lineCount;
            }
        }
              
        lastContentRTy = nowContentRTy;
    }
    //true 表示是处理第一个
    float GetItemPositionY(bool first)
    {
        float y;
        if (first)
            y = itemList.First.Value.anchoredPosition.y;
        else
            y = itemList.Last.Value.anchoredPosition.y;

        return y;
    }
    float GetContentPositionY()
    {
        return contentTR.anchoredPosition.y;
    }

    void SetInfo(ICustomGridItem<DataType> item, int index)
    {
        if(index >=0 && index < datas.Length)
        {
            item.SetInfo(datas[index]);
        }
        else
        {
            item.SetInfo(null);
        }
    }

    void MoveItemGo(bool up)
    {
        if (up)
        {//tou
            for (int i = 0; i < lineCount; i++)
            {
                var item = itemList.First.Value;

                Vector3 pos = item.anchoredPosition;

                pos.y -= line * (GridHeight + space.y);

                item.anchoredPosition = pos;

                itemList.RemoveFirst();

                itemList.AddLast(item);

                //注入数据
                int index = firstLineIndex +(line-1)*lineCount + lineCount + i;
                

                SetInfo(gridList[item.gameObject], index);
                
            }
        }
        else
        {
            for (int i = 0; i < lineCount; i++)
            {
                var item = itemList.Last.Value;

                Vector3 pos = item.anchoredPosition;

                pos.y += line * (GridHeight + space.y);

                item.anchoredPosition = pos;

                itemList.RemoveLast();
                itemList.AddFirst(item);
                int index = firstLineIndex - i - 1;

                SetInfo(gridList[item.gameObject], index);

            }
        }
    }
    void Refresh()
    {
        var value = itemList.First;
        for (int i = firstLineIndex; i < firstLineIndex + itemCountInView; i++)
        {
            if (i >= datas.Length) return;
            if (value == null) return;
            gridList[value.Value.gameObject].SetInfo(datas[i]);
            gridList[value.Value.gameObject].UpdateView();
            value = value.Next;
        }

    }
} 

你可能感兴趣的:(Unity 格子布局复用item滚动列表组件)