之前写过相关组件:https://blog.csdn.net/qq_34987964/article/details/100937002
这次遇到项目需求, 重写并优化了代码
结构上面基本没有变化 , 包含一个使用示例:
代码部分:
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;/*** 视窗ScrollRect垂直自动排序(Item固定长高/节点复用/无限滚动)* ContentParent上面不能有Layout和SizeFitter组件,其他窗口默认ScroView即可* Item需要设置minAnchors(0.5,1),maxAnchors(0.5,1)<以父节点的上边中点为锚点>*/
public class ScrollViewVerticalSort<T> : MonoBehaviour
{//创建节点初始化委托//参数:实例化节点//返回值:对此节点设置属性的Action委托public Func<Transform, Action<T>> CreateEvent;//Viewport节点(可视范围节点)public RectTransform m_Viewport;//Item父节点/预制体节点public RectTransform m_ContentParent;public RectTransform m_ContentItem;//Item高宽private float itemHeight;private float itemWidth;//Itam行列间隔private float spacingWidth;private float spacingHeight;/// <summary>列数量(x轴上排列几个)</summary>private int colCount;/// <summary>行数量(y轴上排列几个)</summary>private int rowCount;//数据容器//private List<object> __dataList = new List<object>();private List<T> __dataList = new List<T>();//开始数据下标private int __startIndex = 0;//节点池列表private List<ItemControll> __releaseList = new List<ItemControll>();private List<ItemControll> __activeList = new List<ItemControll>();//添加数据public void Add( T data){__dataList.Add(data);//刷新视图RefreshContentSize();RefreshContent();}public void AddRange(T[] datas){__dataList.AddRange(datas);//刷新视图RefreshContentSize();RefreshContent();}//移除数据public void Remove(params T[] datas){foreach (var data in datas){__dataList.Remove(data);}//刷新视图RefreshContentSize();RefreshContent();}//清空数据public void Clear(){__dataList.Clear();//刷新视图RefreshContentSize();RefreshContent();}//设置间隔public void SetSpacingWidth(float width){if (spacingWidth != width){spacingWidth = width;//刷新视图RefreshContentSize();RefreshContent();}}public void SetSpacingHeight(float height){if (spacingHeight != height){spacingHeight = height;//刷新视图RefreshContentSize();RefreshContent();}}//设置大小public void SetItemWidth(float width){if (itemWidth != width){itemWidth = width;//刷新视图RefreshContentSize();RefreshContent();}}public void SetItemHeight(float height){if (itemHeight != height){itemHeight = height;//刷新视图RefreshContentSize();RefreshContent();}}//设置Countpublic void SetColCount(int count){if (count < 1){throw new Exception("SET COLCOUNT ERROR: count must >=1 ");}if (colCount != count){colCount = count;//刷新视图RefreshContentSize();RefreshContent();}}public void SetRowCount(int count){if (count < 3){throw new Exception("SET ROWCOUNT ERROR: count must >=3 ");}if (rowCount != count){rowCount = count;//刷新视图RefreshContentSize();RefreshContent();} }protected virtual void Awake(){//尝试获取节点TryGetChildren();//初始化InitialRectSize();InitialListeners();}protected virtual void TryGetChildren(){if (m_Viewport == null){m_Viewport = transform.Find("Viewport") as RectTransform;}if (m_ContentParent == null && m_Viewport != null){m_ContentParent = m_Viewport.Find("Content") as RectTransform;}if (m_ContentItem == null && m_ContentParent != null){m_ContentItem = m_ContentParent.Find("Item") as RectTransform;}m_ContentItem?.gameObject.SetActive(false);}//计算初始窗口元素大小 private void InitialRectSize(){if (m_Viewport != null && m_ContentItem != null){itemWidth = m_ContentItem.sizeDelta.x;itemHeight = m_ContentItem.sizeDelta.y;int x_n = (int)(m_Viewport.rect.size.x / (itemWidth + spacingWidth));colCount = x_n > 1 ? x_n : 1;int y_n = (int)(m_Viewport.rect.size.y / (itemHeight + spacingHeight));y_n += 2; //上下额外显示一个rowCount = y_n > 3 ? y_n : 3;}}//刷新mContentParent节点大小private void RefreshContentSize(){if (m_ContentParent != null && colCount > 0 && rowCount > 0) {int y_n = __dataList.Count / colCount;if (__dataList.Count % colCount!=0){y_n++;}m_ContentParent.sizeDelta = new Vector2(m_ContentParent.sizeDelta.x, y_n * (itemHeight + spacingHeight));}}//刷新Content上面的元素显示private void RefreshContent(){if (m_ContentParent == null || m_ContentItem == null || CreateEvent == null) {return;}//回收节点for (int i = __activeList.Count - 1; i >= 0; i--)ReleaseItem(__activeList[i]);//显示节点var total = rowCount * colCount;for (int i = 0; i < total; i++){var index = __startIndex + i;if (index < __dataList.Count){var item = GetItem();item.Set(__dataList[index]);item.SetActive(true);item.transform.localPosition = GetPosition(index);item.transform.SetAsLastSibling();item.transform.name = index.ToString();}else break;}}//初始化滑动监听事件private void InitialListeners(){var rect = GetComponent<ScrollRect>();rect.onValueChanged.AddListener((vec2) =>{var index = GetStartIndex(m_ContentParent.localPosition.y);if (index != __startIndex && index >= 0){var total = rowCount * colCount;if (Math.Abs(__startIndex - index) < total && __activeList.Count >= total){//Debug.Log(string.Format("{0},{1},{2},{3}", Math.Abs(__startIndex - index), index, total, __activeList.Count));//尝试只回收不可见的节点(部分仍然可见)while (__startIndex != index){int now_index = -1;ItemControll item = null;if (__startIndex > index){//回收下面,往上显示__startIndex--;now_index = __startIndex;//回收最后一个,并且显示到第一个var last = __activeList.Count - 1;item = __activeList[last];__activeList.RemoveAt(last);__activeList.Insert(0, item);//设置属性item.transform.SetAsFirstSibling();}else{//回收上面,往下显示now_index = __startIndex + total;__startIndex++;//回收第一个一个,并且显示到最后一个item = __activeList[0];__activeList.RemoveAt(0);__activeList.Insert(__activeList.Count, item);//设置属性item.transform.SetAsLastSibling();}item.Set(__dataList[now_index]);item.SetActive(true);item.transform.localPosition = GetPosition(now_index);item.transform.name = now_index.ToString();}}else{//RefreshContent回收所有节点然后刷新显示__startIndex = index;RefreshContent();}}});}//通过Content.y坐标,计算从第n个开始显示private int GetStartIndex(float pos_y){int index =(int)(pos_y / (itemHeight + spacingHeight));return index * colCount;}//通过Index获取此节点位置private Vector3 GetPosition(int index){int x = index % colCount;int y = index / colCount;return new Vector3(x * (itemWidth + spacingWidth) + itemWidth * 0.5f, -y * (itemHeight + spacingHeight) - itemHeight * 0.5f, 0);}#region 节点池方法private ItemControll GetItem(){ItemControll item = null;if (__releaseList.Count > 0){item = __releaseList[0];__releaseList.RemoveAt(0);}if (item == null){var trf = Instantiate(m_ContentItem);trf.SetParent(m_ContentParent);trf.localScale = Vector3.one;//初始化节点,并得到设置属性方法var func = CreateEvent(trf);item = new ItemControll(trf, func);}__activeList.Add(item);return item;}private void ReleaseItem(ItemControll item){item.SetActive(false);__activeList.Remove(item);if (!__releaseList.Contains(item)){__releaseList.Add(item);}}#endregion#region 节点控制器public class ItemControll{//对象节点public Transform transform { get; private set; }//设置节点属性委托private Action<T> set_callback;public ItemControll(Transform trf, Action<T> func){this.transform = trf;this.set_callback = func;}public void Set(T param){set_callback?.Invoke(param);}public void SetActive(bool active){transform.gameObject.SetActive(active);}}#endregion
}#region 扩展Data类
public class ScrollViewVerticalSort : ScrollViewVerticalSort<object>
{private void Start(){TestScrollView.TestData(gameObject);}
}
#endregion#region 测试类/测试数据
public class TestScrollView
{public static void TestData(GameObject obj){var mono = obj.GetComponent<ScrollViewVerticalSort>();mono.CreateEvent = (trf) =>{TestScrollViewData data = null;var text = trf.Find("Text").GetComponent<Text>();var image = trf.Find("Image").GetComponent<Image>();trf.GetComponent<Button>().onClick.AddListener(() =>{Debug.Log("当前点击序号:" + data.index);});return (agrs) =>{data = agrs as TestScrollViewData;text.text = data.index.ToString();image.color = data.color;};};mono.SetSpacingWidth(10);mono.SetSpacingHeight(10);//AddDatavar color_table = new Color[]{Color.black,Color.blue,Color.clear,Color.cyan,Color.gray,Color.green,Color.red,Color.magenta};var list = new List<TestScrollViewData>();for (int i = 1; i < 50000; i++){var data = new TestScrollViewData(){index = i,color = color_table[UnityEngine.Random.Range(0, color_table.Length)]};list.Add(data);}mono.AddRange(list.ToArray());//Debug.Log(string.Format("rowCount={0},colCount={1},itemHeight={2},itemWidth={3}", mono.rowCount, mono.colCount, mono.itemHeight, mono.itemWidth));}public class TestScrollViewData{public int index;public Color color;}
}
#endregion