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,下方是使用例子欢迎下载
示例下载地址