123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- // ## 生成滑动列表必须以下步骤:
- // 1. 持有RecycleView对象rv,rv.Init(callBackFunc)
- // 2. 刷新整个列表(首次调用和数量变化时调用): ShowList(int count)
- // 3. 回调: Func(GameObject cell, int index)
- // ----------
- // 功能接口看代码,案例详见RecycleViewTest.cs
- // 刷新单个项: UpdateCell(int index)
- // 刷新列表数据(无数量变化时调用): UpdateList()
- // 定位到索引所在当前列表的位置 GoToCellPos(int index)
- using UnityEngine;
- using UnityEngine.UI;
- using System.Collections.Generic;
- using UnityEngine.EventSystems;
- using System;
- public enum E_Direction
- {
- Horizontal,
- Vertical
- }
- public class RecycleView : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
- {
- public GameObject firstArrow;
- public GameObject endArrow;
- public E_Direction dir = E_Direction.Vertical;
- public bool isShowArrow = false;
- public int lines = 1; // 默认显示1行
- public float squareSpacing = 4f; // 方阵间距
- public GameObject cell; //指定的cell
- public Vector2 Spacing = Vector2.zero;
- public float row = 0f; // 行间距
- public float col = 0f; // 列间距
- protected Action<GameObject, int> FuncCallBackFunc;
- protected Action<GameObject, int> FuncOnClickCallBack;
- protected Action<int, bool, GameObject> FuncOnButtonClickCallBack;
- protected float planeW;
- protected float planeH;
- protected float contentW;
- protected float contentH;
- protected float cellW;
- protected float cellH;
- private bool isInit = false;
- protected GameObject content;
- protected ScrollRect scrollRect;
- protected RectTransform rectTrans;
- protected RectTransform contentRectTrans;
- protected int maxCount = -1; //列表数量
- protected int minIndex = -1;
- protected int maxIndex = -1;
- //记录 物体的坐标 和 物体
- protected struct CellInfo
- {
- public Vector3 pos;
- public GameObject obj;
- };
- protected CellInfo[] cellInfos;
- protected bool isClearList = false; //是否清空列表
- // 对象池
- protected Stack<GameObject> Pool = new Stack<GameObject>();
- protected bool isInited = false;
- public virtual void Init(Action<GameObject, int> callBack)
- {
- Init(callBack, null);
- }
- public virtual void Init(Action<GameObject, int> callBack, Action<GameObject, int> onClickCallBack,
- Action<int, bool, GameObject> onButtonClickCallBack)
- {
- if (onButtonClickCallBack != null)
- {
- FuncOnButtonClickCallBack = onButtonClickCallBack;
- }
- Init(callBack, onClickCallBack);
- }
- public virtual void Init(Action<GameObject, int> callBack, Action<GameObject, int> onClickCallBack)
- {
- DisposeAll();
- FuncCallBackFunc = callBack;
- if (onClickCallBack != null)
- {
- FuncOnClickCallBack = onClickCallBack;
- }
- if (isInit) return;
- content = this.GetComponent<ScrollRect>().content.gameObject;
- if (cell == null)
- {
- cell = content.transform.GetChild(0).gameObject;
- }
- // ////////////////////** Cell 处理 **////////////////////
- // m_CellGameObject.transform.SetParent(m_Content.transform.parent, false);
- SetPoolsObj(cell);
- RectTransform cellRectTrans = cell.GetComponent<RectTransform>();
- cellRectTrans.pivot = new Vector2(0f, 1f);
- CheckAnchor(cellRectTrans);
- cellRectTrans.anchoredPosition = Vector2.zero;
- // 记录 Cell 信息
- cellH = cellRectTrans.rect.height;
- cellW = cellRectTrans.rect.width;
- // 记录 Plane 信息
- rectTrans = GetComponent<RectTransform>();
- Rect planeRect = rectTrans.rect;
- planeH = planeRect.height;
- planeW = planeRect.width;
- // 记录 Content 信息
- contentRectTrans = content.GetComponent<RectTransform>();
- Rect contentRect = contentRectTrans.rect;
- contentH = contentRect.height;
- contentW = contentRect.width;
- // 记录间距信息 如果存在行列设置就引用,没有使用方阵间距
- row = Spacing.x;
- col = Spacing.y;
- if (row == 0 && col == 0) row = col = squareSpacing;
- else squareSpacing = 0;
- contentRectTrans.pivot = new Vector2(0f, 1f);
- //m_ContentRectTrans.sizeDelta = new Vector2 (planeRect.width, planeRect.height);
- //m_ContentRectTrans.anchoredPosition = Vector2.zero;
- CheckAnchor(contentRectTrans);
- scrollRect = this.GetComponent<ScrollRect>();
- scrollRect.onValueChanged.RemoveAllListeners();
- //添加滑动事件
- scrollRect.onValueChanged.AddListener(delegate (Vector2 value) { ScrollRectListener(value); });
- if (firstArrow != null || endArrow != null)
- {
- scrollRect.onValueChanged.AddListener(delegate (Vector2 value) { OnDragListener(value); });
- OnDragListener(Vector2.zero);
- }
- //InitScrollBarGameObject(); // 废弃
- isInit = true;
- }
- // 检查 Anchor 是否正确
- private void CheckAnchor(RectTransform rectTrans)
- {
- if (dir == E_Direction.Vertical)
- {
- if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
- (rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(1, 1))))
- {
- rectTrans.anchorMin = new Vector2(0, 1);
- rectTrans.anchorMax = new Vector2(1, 1);
- }
- }
- else
- {
- if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
- (rectTrans.anchorMin == new Vector2(0, 0) && rectTrans.anchorMax == new Vector2(0, 1))))
- {
- rectTrans.anchorMin = new Vector2(0, 0);
- rectTrans.anchorMax = new Vector2(0, 1);
- }
- }
- }
- // 实时刷新列表时用
- public virtual void UpdateList()
- {
- for (int i = 0, length = cellInfos.Length; i < length; i++)
- {
- CellInfo cellInfo = cellInfos[i];
- if (cellInfo.obj != null)
- {
- float rangePos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
- if (!IsOutRange(rangePos))
- {
- Func(FuncCallBackFunc, cellInfo.obj, true);
- }
- }
- }
- }
- // 刷新某一项
- public void UpdateCell(int index)
- {
- CellInfo cellInfo = cellInfos[index - 1];
- if (cellInfo.obj != null)
- {
- float rangePos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
- if (!IsOutRange(rangePos))
- {
- Func(FuncCallBackFunc, cellInfo.obj);
- }
- }
- }
- public virtual void ShowList(string numStr)
- {
- }
- public virtual void ShowList(int num)
- {
- minIndex = -1;
- maxIndex = -1;
- //-> 计算 Content 尺寸
- if (dir == E_Direction.Vertical)
- {
- float contentSize = (col + cellH) * Mathf.CeilToInt((float)num / lines);
- contentH = contentSize;
- contentW = contentRectTrans.sizeDelta.x;
- contentSize = contentSize < rectTrans.rect.height ? rectTrans.rect.height : contentSize;
- contentRectTrans.sizeDelta = new Vector2(contentW, contentSize);
- if (num != maxCount)
- {
- contentRectTrans.anchoredPosition = new Vector2(contentRectTrans.anchoredPosition.x, 0);
- }
- }
- else
- {
- float contentSize = (row + cellW) * Mathf.CeilToInt((float)num / lines);
- contentW = contentSize;
- contentH = contentRectTrans.sizeDelta.x;
- contentSize = contentSize < rectTrans.rect.width ? rectTrans.rect.width : contentSize;
- contentRectTrans.sizeDelta = new Vector2(contentSize, contentH);
- if (num != maxCount)
- {
- contentRectTrans.anchoredPosition = new Vector2(0, contentRectTrans.anchoredPosition.y);
- }
- }
- //-> 计算 开始索引
- int lastEndIndex = 0;
- //-> 过多的物体 扔到对象池 ( 首次调 ShowList函数时 则无效 )
- if (isInited)
- {
- lastEndIndex = num - maxCount > 0 ? maxCount : num;
- lastEndIndex = isClearList ? 0 : lastEndIndex;
- int count = isClearList ? cellInfos.Length : maxCount;
- for (int i = lastEndIndex; i < count; i++)
- {
- if (cellInfos[i].obj != null)
- {
- SetPoolsObj(cellInfos[i].obj);
- cellInfos[i].obj = null;
- }
- }
- }
- //-> 以下四行代码 在for循环所用
- CellInfo[] tempCellInfos = cellInfos;
- cellInfos = new CellInfo[num];
- //-> 1: 计算 每个Cell坐标并存储 2: 显示范围内的 Cell
- for (int i = 0; i < num; i++)
- {
- // * -> 存储 已有的数据 ( 首次调 ShowList函数时 则无效 )
- if (maxCount != -1 && i < lastEndIndex)
- {
- CellInfo tempCellInfo = tempCellInfos[i];
- //-> 计算是否超出范围
- float rPos = dir == E_Direction.Vertical ? tempCellInfo.pos.y : tempCellInfo.pos.x;
- if (!IsOutRange(rPos))
- {
- //-> 记录显示范围中的 首位index 和 末尾index
- minIndex = minIndex == -1 ? i : minIndex; //首位index
- maxIndex = i; // 末尾index
- if (tempCellInfo.obj == null)
- {
- tempCellInfo.obj = GetPoolsObj();
- }
- tempCellInfo.obj.transform.GetComponent<RectTransform>().anchoredPosition = tempCellInfo.pos;
- tempCellInfo.obj.name = i.ToString();
- tempCellInfo.obj.SetActive(true);
- Func(FuncCallBackFunc, tempCellInfo.obj);
- }
- else
- {
- SetPoolsObj(tempCellInfo.obj);
- tempCellInfo.obj = null;
- }
- cellInfos[i] = tempCellInfo;
- continue;
- }
- CellInfo cellInfo = new CellInfo();
- float pos = 0; //坐标( isVertical ? 记录Y : 记录X )
- float rowPos = 0; //计算每排里面的cell 坐标
- // * -> 计算每个Cell坐标
- if (dir == E_Direction.Vertical)
- {
- pos = cellH * Mathf.FloorToInt(i / lines) +
- col * Mathf.FloorToInt(i / lines);
- rowPos = cellW * (i % lines) + row * (i % lines);
- cellInfo.pos = new Vector3(rowPos, -pos, 0);
- }
- else
- {
- pos = cellW * Mathf.FloorToInt(i / lines) + row * Mathf.FloorToInt(i / lines);
- rowPos = cellH * (i % lines) + col * (i % lines);
- cellInfo.pos = new Vector3(pos, -rowPos, 0);
- }
- //-> 计算是否超出范围
- float cellPos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
- if (IsOutRange(cellPos))
- {
- cellInfo.obj = null;
- cellInfos[i] = cellInfo;
- continue;
- }
- //-> 记录显示范围中的 首位index 和 末尾index
- minIndex = minIndex == -1 ? i : minIndex; //首位index
- maxIndex = i; // 末尾index
- //-> 取或创建 Cell
- GameObject cell = GetPoolsObj();
- cell.transform.GetComponent<RectTransform>().anchoredPosition = cellInfo.pos;
- cell.gameObject.name = i.ToString();
-
- //-> 存数据
- cellInfo.obj = cell;
- cellInfos[i] = cellInfo;
- //-> 回调 函数
- Func(FuncCallBackFunc, cell);
- }
- maxCount = num;
- isInited = true;
- OnDragListener(Vector2.zero);
- }
- /// <summary>
- /// 通过index定位到GameObject
- /// </summary>
- /// <param name="index"></param>
- public void GoToCellPos(int index)
- {
- // 当前索引所在行的第一个索引
- int theFirstIndex = index - index % lines;
- // 假设在第一行最大索引
- var tmpIndex = theFirstIndex + maxIndex;
- int theLastIndex = tmpIndex > maxCount - 1 ? maxCount - 1 : tmpIndex;
- // 如果最大索引就是边界的话,边界的
- if (theLastIndex == maxCount - 1)
- {
- // 余数不为0的情况下,第一个索引位置需要考虑最大数到最后显示位置的边距
- var shortOfNum = maxCount % lines == 0 ? 0 : lines - maxCount % lines;
- theFirstIndex = theLastIndex - maxIndex + shortOfNum;
- }
- Vector2 newPos = cellInfos[theFirstIndex].pos;
- if (dir == E_Direction.Vertical)
- {
- contentRectTrans.anchoredPosition = new Vector2(contentRectTrans.anchoredPosition.x, -newPos.y);
- }
- else
- {
- contentRectTrans.anchoredPosition = new Vector2(-newPos.x, contentRectTrans.anchoredPosition.y);
- }
- // print(string.Format("index: {0}, theFirstIndex: {1},maxIndex:{2} theLastIndex:{3}", index, theFirstIndex,
- // maxIndex, theLastIndex));
- }
- #if UNITY_EDITOR
- //public void LogRecycleView()
- //{
- // // 拿到容器基础信息
- // print("----------------------------------------------------------------------------");
- // print("Direction: " + dir);
- // print("Lines: " + lines);
- // print(string.Format("minIndex: {0} , maxIndex: {1}", minIndex, maxIndex));
- // print("Capacity: " + (maxIndex - minIndex + 1));
- // print("----------------------------------------------------------------------------");
- //}
- #endif
- // 更新滚动区域的大小
- public void UpdateSize()
- {
- Rect rect = GetComponent<RectTransform>().rect;
- planeH = rect.height;
- planeW = rect.width;
- }
- // 滑动事件
- protected virtual void ScrollRectListener(Vector2 value)
- {
- UpdateCheck();
- }
- private void UpdateCheck()
- {
- if (cellInfos == null) return;
- // 检查超出范围
- for (int i = 0, length = cellInfos.Length; i < length; i++)
- {
- CellInfo cellInfo = cellInfos[i];
- GameObject obj = cellInfo.obj;
- Vector3 pos = cellInfo.pos;
- float rangePos = dir == E_Direction.Vertical ? pos.y : pos.x;
- // 判断是否超出显示范围
- if (IsOutRange(rangePos))
- {
- // 把超出范围的cell 扔进 poolsObj里
- if (obj != null)
- {
- SetPoolsObj(obj);
- cellInfos[i].obj = null;
- }
- }
- else
- {
- if (obj == null)
- {
- // 优先从 poolsObj中 取出 (poolsObj为空则返回 实例化的cell)
- GameObject cell = GetPoolsObj();
- cell.transform.localPosition = pos;
- cell.gameObject.name = i.ToString();
- cellInfos[i].obj = cell;
- Func(FuncCallBackFunc, cell);
- }
- }
- }
- }
- // 判断是否超出显示范围
- protected bool IsOutRange(float pos)
- {
- Vector3 listP = contentRectTrans.anchoredPosition;
- if (dir == E_Direction.Vertical)
- {
- if (pos + listP.y > cellH || pos + listP.y < -rectTrans.rect.height)
- {
- return true;
- }
- }
- else
- {
- if (pos + listP.x < -cellW || pos + listP.x > rectTrans.rect.width)
- {
- return true;
- }
- }
- return false;
- }
- //取出 cell
- protected virtual GameObject GetPoolsObj()
- {
- GameObject cell = null;
- if (Pool.Count > 0) cell = Pool.Pop();
- if (cell == null) cell = Instantiate(this.cell) as GameObject;
- cell.transform.SetParent(content.transform);
- cell.transform.localScale = Vector3.one;
- SetActive(cell, true);
- return cell;
- }
- //存入 cell
- protected virtual void SetPoolsObj(GameObject cell)
- {
- if (cell != null)
- {
- Pool.Push(cell);
- SetActive(cell, false);
- }
- }
- //回调
- protected void Func(Action<GameObject, int> func, GameObject selectObject, bool isUpdate = false)
- {
- int index = int.Parse(selectObject.name);
- if (func != null)
- {
- func(selectObject, index);
- }
- }
- public void DisposeAll()
- {
- if (FuncCallBackFunc != null) FuncCallBackFunc = null;
- if (FuncOnClickCallBack != null) FuncOnClickCallBack = null;
- if (FuncOnButtonClickCallBack != null) FuncOnButtonClickCallBack = null;
- }
- protected void OnDestroy()
- {
- DisposeAll();
- }
- public virtual void OnClickCell(GameObject cell)
- {
- }
- //-> ExpandCircularScrollView 函数
- public virtual void OnClickExpand(int index)
- {
- }
- //-> FlipCircularScrollView 函数
- public virtual void SetToPageIndex(int index)
- {
- }
- public virtual void OnBeginDrag(PointerEventData eventData)
- {
- }
- public void OnDrag(PointerEventData eventData)
- {
- }
- public virtual void OnEndDrag(PointerEventData eventData)
- {
- }
- protected void OnDragListener(Vector2 value)
- {
- float normalizedPos = dir == E_Direction.Vertical
- ? scrollRect.verticalNormalizedPosition
- : scrollRect.horizontalNormalizedPosition;
- if (dir == E_Direction.Vertical)
- {
- if (contentH - rectTrans.rect.height < 10)
- {
- SetActive(firstArrow, false);
- SetActive(endArrow, false);
- return;
- }
- }
- else
- {
- if (contentW - rectTrans.rect.width < 10)
- {
- SetActive(firstArrow, false);
- SetActive(endArrow, false);
- return;
- }
- }
- if (normalizedPos >= 0.9)
- {
- SetActive(firstArrow, false);
- SetActive(endArrow, true);
- }
- else if (normalizedPos <= 0.1)
- {
- SetActive(firstArrow, true);
- SetActive(endArrow, false);
- }
- else
- {
- SetActive(firstArrow, true);
- SetActive(endArrow, true);
- }
- }
- public GameObject GetCellGameObject(int index)
- {
- // 为了保证拿到正确数据,根据index应该-1拿到正确数据
- return cellInfos[--index].obj;
- }
- public int GetCellIndex(GameObject obj)
- {
- // 第0号是模板,所以列表中索引应该-1
- return Convert.ToInt32(obj.name) - 1;
- }
- private static void SetActive(GameObject obj, bool isActive)
- {
- if (obj != null)
- {
- obj.SetActive(isActive);
- }
- }
- }
|