Unity 支持水平,竖直。无限轮播 ,基于UGUI的ScrollView组件

本文介绍如何使用Unity基于UGUI的ScrollView组件创建支持水平和竖直无限滚动的列表,包括设置参数、数据布局和滑动事件处理。通过SetParam方法配置数据源、显示方式和布局参数,适用于动态数据展示。
摘要由CSDN通过智能技术生成

Unity 支持水平,竖直。无限轮播 ,基于UGUI的ScrollView组件

1,实图
在这里插入图片描述
2,代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 支持 水平 和 垂直无限拖动
/// </summary>
/// <typeparam name="T"></typeparam>
public class BaseList<T>
{
    public delegate Transform SetElement(Transform transform, T itemData);
    /// <summary>
    /// //显示列数
    /// </summary>
    public int 列数 = 1;
    /// <summary>
    /// 行距
    /// </summary>
    public float 行间距;
    /// <summary>
    /// 列距
    /// </summary>
    public float 列间距;
    /// <summary>
    /// // 可显示行数
    /// </summary>
    private int 行数 = 1;
    private Transform content;         //List的基本组件
    private float parentWidth = 0;      //List的宽度
    private float parentHeight = 0;      //List的高度
    private Transform itemTransform;    //显示的ITem
    private float itemWidth = 0;        //子物体宽度
    private float itemHeight = 0;       //子物体高度
    private int 显示数量 = 0;          //显示的子物体数量
    private float 可视范围 = 0;      //可视范围
    private float 内容总长度 = 0;    //总长度
    private bool is水平滑动;
    private float contentStartPos = 0;         //开始位置

    // private float maxY;                 //最大可显示高度
    // private float minY;                 //最小可显示高度

    private Transform rootTransform = null;

    //private List<string> dataList = null;

    private List<T> dataList;
    private SetElement setElement;

    private Stack<Transform> itemStack; //搞个栈存储Item,方便存取

    private Dictionary<int, Transform> itemDictionary = new Dictionary<int, Transform>();


    /// <summary>
    /// 第一个参数是设置的列表数据
    /// 第二个参数是设置显示方式的方法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="setElement"></param> 
    public void SetParam(List<T> list, SetElement setElement, Transform root, float rowDistance, float columnDistance, bool isH)
    {
        is水平滑动 = isH;
        行间距 = rowDistance;
        列间距 = columnDistance;
        rootTransform = root;
        dataList = list;
        this.setElement = setElement;
        InitParameter();
    }

    /// <summary>
    /// 参数初始化
    /// </summary>
    private void InitParameter()
    {
        //可视范围
        content = rootTransform.Find("Viewport/Content");

        //要实例化的item
        itemTransform = rootTransform.Find("Viewport/Item").transform;
        itemTransform.gameObject.SetActive(false);
        Rect itemRect = itemTransform.GetComponent<RectTransform>().rect;
        itemWidth = itemRect.width;
        itemHeight = itemRect.height;
        //可视范围 - 父物体
        parentWidth = rootTransform.GetComponent<RectTransform>().rect.width;
        parentHeight = rootTransform.GetComponent<RectTransform>().rect.height;

        if (!is水平滑动)  // 垂直
        {
            contentStartPos = content.localPosition.y;
            可视范围 = rootTransform.GetComponent<RectTransform>().rect.height;
            //int maxcolumnCount = (int)(parentWidth / itemWidth);
            // if (maxcolumnCount < 列数)
            //列数 = maxcolumnCount;

            行数 = (int)((parentHeight - (行间距 + itemHeight / 2)) / (行间距 + itemHeight) + 1);

            // maxY = itemHeight / 2;
            // minY = (行间距 + itemHeight) * 行数;
        }
        else // 水平滑动
        {
            contentStartPos = content.localPosition.x;
            可视范围 = rootTransform.GetComponent<RectTransform>().rect.width;

            列数 = (int)((parentWidth - (列间距 + itemWidth / 2)) / (列间距 + itemWidth) + 1);

        }
        显示数量 = 列数 * 行数;
        Debug.Log("列数:" + 列数 + "行数:" + 行数 + "显示数量:" + 显示数量);
        SetData();
    }

    /// <summary>
    /// 设置元素
    /// </summary>
    /// <param name="list"></param>
    public void SetData()
    {
        int maxCount = dataList.Count;
        int index = 0;
        if (!is水平滑动)
        {
            内容总长度 = (itemHeight + 行间距) * maxCount / 列数;
            //重新计算Content的宽高用来装Item,但是这个x轴的宽度很神奇填0是ok的但是不规范坐等大神解决(自行调试发现问题我暂时没有解决)
            content.GetComponent<RectTransform>().sizeDelta = new Vector2(content.GetComponent<RectTransform>().sizeDelta.x, 内容总长度 + 10);
        }
        else
        {
            内容总长度 = (itemWidth + 列间距) * maxCount / 行数;
            //重新计算Content的宽高用来装Item,但是这个x轴的宽度很神奇填0是ok的但是不规范坐等大神解决(自行调试发现问题我暂时没有解决)
            content.GetComponent<RectTransform>().sizeDelta = new Vector2(内容总长度 + 10, content.GetComponent<RectTransform>().sizeDelta.y);
        }
        Debug.Log("内容总长度:" + 内容总长度);

        ScrollRect scrollRect = rootTransform.GetComponent<ScrollRect>();
        if (scrollRect != null)
        {
            scrollRect.onValueChanged.RemoveAllListeners();
            scrollRect.onValueChanged.AddListener(OnValueChange);   //监听滑动值的改变,传入的是位置信息 
        }
        if (!is水平滑动)
        {
            for (int row = 0; row < 行数 + 1; row++)
            {
                for (int column = 0; column < 列数; column++)
                {
                    if (index >= maxCount)
                        return;
                    Transform itemTrans = setElement(GameObject.Instantiate(itemTransform.gameObject, content.transform).transform, dataList[index]);
                    itemTrans.gameObject.SetActive(true);
                    //itemTrans.GetComponent<RectTransform>().anchorMin = new Vector2(0, 1);
                    //itemTrans.GetComponent<RectTransform>().anchorMax = new Vector2(0, 1);
                    // float x = (column * (itemWidth + 列间距) + itemWidth / 2 + 列间距);
                    //float x = parentWidth / 2f;
                    //float x = 0;
                    //前面物体的距离=行数*(物体高度+物体间离)
                    //自己再往后退半个+间距
                    float y = -(row * (itemHeight + 行间距) + itemHeight / 2 + 行间距);

                    itemTrans.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, y); //设置位置

                    //itemTrans.localPosition = new Vector2(0, y);   
                    // Debug.Log("设置位置:" + itemTrans.localPosition);
                    itemDictionary.Add(index, itemTrans);
                    index++;
                }
                if (index >= maxCount)
                    return;
            }
        }
        else
        {
            for (int col = 0; col < 列数 + 1; col++)
            {
                for (int row = 0; row < 行数; row++)
                {
                    if (index >= maxCount)
                        return;

                    Transform itemTrans = setElement(GameObject.Instantiate(itemTransform.gameObject, content.transform).transform, dataList[index]);

                    itemTrans.gameObject.SetActive(true);
                    //itemTrans.GetComponent<RectTransform>().anchorMin = new Vector2(0, 1);
                    //itemTrans.GetComponent<RectTransform>().anchorMax = new Vector2(0, 1);
                    float x = (col * (itemWidth + 列间距) + itemWidth / 2 + 列间距);
                    //前面物体的距离=行数*(物体高度+物体间离)
                    //自己再往后退半个+间距
                    // float y = -(row * (itemHeight + 行间距) + itemHeight / 2 + 行间距);
                    //float y = parentHeight / 2f;
                    itemTrans.GetComponent<RectTransform>().anchoredPosition = new Vector2(x, 0);   //设置位置            
                    itemDictionary.Add(index, itemTrans);
                    index++;
                }
                if (index >= maxCount)
                    return;

            }
        }
    }


    /// <summary>
    /// 监听List滑动
    /// 记录一下思路,我们需要的是显示在可视范围内的物体
    /// 这个移动监听事件的值是0-1,最上面是1,最下面是0
    /// 我们现在有最大长度,可视长度,可视长度/最大长度 = 可视比例 比如长度是10,可视距离是1,我们的可视比例是1/10
    /// 那么如果我们把页面拉到0.2的时候,只要子物体在0.2*最大长度和0.2*(1/10)*最大长度的范围内,我们就让它进行显示,超出这个范围的就存到栈内
    /// </summary>
    /// <param name="vector"></param>
    public void OnValueChange(Vector2 vector)
    {
        //获取当前显示的最后一个
        int showIndex = GetShowIndex();
        //不能小于第一个,不能大到剩下的item不足以容下完整一页
        if (showIndex < 1) showIndex = 1;
        //else if (startIndex - (dataList.Count - showCount) < 0) startIndex = (dataList.Count - showCount);
        //可视比例
        // float overallProp = 可视范围 / 内容总长度;
        // float maxY = content.transform.InverseTransformPoint(new Vector3(0, vector.y * overallLength, 0)).y;   //最大高度
        // float minY = content.transform.InverseTransformPoint(new Vector3(0, vector.y * overallProp, 0)).y;     //最小高度

        //int index = showIndex - 1; //虽然找到了开始位置,但是数组下标是从0开始的

        //int endIndex = startIndex + (dataList.Count - 显示数量);

        List<int> uplist = new List<int>();
        List<int> downList = new List<int>();
        //清空不在范围内的数据存到队列中
        foreach (int key in itemDictionary.Keys)
        {
            if (!is水平滑动)
            {
                //当前物体在可视范围之上
                if (key < showIndex - 1 && key + ((列数 * (行数 + 1))) < dataList.Count)
                    uplist.Add(key);
                //当前物体在可视范围之下
                if (key > showIndex + (列数 * (行数 + 1)) - 列数 - 1)
                    downList.Add(key);
            }
            else
            {
                //当前物体在可视范围之左
                if (key < showIndex - 1 && key + (行数 * (列数 + 1)) < dataList.Count)
                    uplist.Add(key);
                //当前物体在可视范围之右
                if (key > showIndex + (行数 * (列数 + 1)) - 行数 - 1)
                    downList.Add(key);
            }
        }
        //删除上面的表示物体往下滑了,
        //我们要填充的是该位置往下拉可视范围数量
        foreach (int cursor in uplist)
        {
            Transform trans;
            if (itemDictionary.TryGetValue(cursor, out trans))
            {
                itemDictionary.Remove(cursor);
                if (!is水平滑动)
                {
                    int row = cursor / 列数 + (列数 * (行数 + 1)) / 列数;  //拉到第几行                  
                    int indexPos = cursor + (列数 * (行数 + 1));
                    float colum = -(row * (itemHeight + 行间距) + itemHeight / 2 + 行间距);   //计算出该行位置
                    if ((列数 * (行数 + 1)) + cursor < dataList.Count)
                    {
                        trans = setElement(trans, dataList[(列数 * (行数 + 1)) + cursor]);
                        trans.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, colum);
                        //trans.localPosition = new Vector2(trans.localPosition.x, colum);
                        itemDictionary.Add(indexPos, trans);
                    }
                }
                else
                {
                    int col = cursor / 行数 + (行数 * (列数 + 1)) / 行数;  //拉到第几列
                    int indexPos = cursor + (行数 * (列数 + 1));
                    float colum = (col * (itemWidth + 列间距) + itemWidth / 2 + 列间距);   //计算出该列位置
                    if ((行数 * (列数 + 1)) + cursor < dataList.Count)
                    {
                        trans = setElement(trans, dataList[(行数 * (列数 + 1)) + cursor]);
                        trans.GetComponent<RectTransform>().anchoredPosition = new Vector2(colum, 0);
                        //trans.localPosition = new Vector2(colum, trans.localPosition.y);
                        itemDictionary.Add(indexPos, trans);
                    }
                }
            }
        }

        //删除上面的表示物体往下滑了,
        //我们要填充的是该位置往下拉可视范围数量
        foreach (int cursor in downList)
        {
            Transform trans;
            if (itemDictionary.TryGetValue(cursor, out trans))
            {
                itemDictionary.Remove(cursor);
                if (!is水平滑动)
                {
                    int row = cursor / 列数 - (列数 * (行数 + 1)) / 列数;  //拉到第几行
                    int indexPos = cursor - (列数 * (行数 + 1));
                    float rowPos = -(row * (itemHeight + 行间距) + itemHeight / 2 + 行间距);  //计算出该行位置
                    trans = setElement(trans, dataList[cursor - (列数 * (行数 + 1))]);
                    trans.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, rowPos);
                    //trans.localPosition = new Vector2(trans.localPosition.x, rowPos);
                    itemDictionary.Add(indexPos, trans);
                }
                else
                {
                    int col = cursor / 行数 - (行数 * (列数 + 1)) / 行数;  //拉到第几列
                    int indexPos = cursor - (行数 * (列数 + 1));
                    float colPos = (col * (itemWidth + 列间距) + itemWidth / 2 + 列间距);  //计算出该列位置
                    trans = setElement(trans, dataList[cursor - (行数 * (列数 + 1))]);
                    trans.GetComponent<RectTransform>().anchoredPosition = new Vector2(colPos, 0);
                    //trans.localPosition = new Vector2(colPos, trans.localPosition.y);
                    itemDictionary.Add(indexPos, trans);
                }

            }
        }

    }

    /// <summary>
    /// 获取到要从第几个位置开始显示
    /// 这里的做法就是最开始的位置减去当前位置
    /// 往下滑值越低,往上滑值越高。
    /// (初始位置-当前位置)/(item垂直距离+item高度)+1 = 从第几行开始显示
    /// 行数*3+1 
    /// </summary>
    public int GetShowIndex()
    {
        float startPos = contentStartPos;
        float currentPos;
        int line = 0;
        int startIndex = -1;
        if (!is水平滑动)
        {
            currentPos = content.localPosition.y;
            line = ((int)((currentPos - startPos) / (itemHeight + 行间距)) + 1);
            startIndex = line * 列数 - 列数 + 1;
        }
        else
        {
            currentPos = content.localPosition.x;
            line = ((int)(Mathf.Abs(currentPos - startPos) / (itemWidth + 列间距)) + 1);
            startIndex = line * 行数 - 行数 + 1;
        }
        return startIndex;
    }


}



3,使用时调用

 BaseList<T> baseList = new BaseList<T>();
        baseList.SetParam(list, setElement, this.transform, rowDistance, columnDistance, is水平滑动);

4,下方是使用例子欢迎下载
示例下载地址

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

unity_YTWJJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值