using UnityEngine; using System.Collections.Generic; using XUtliPoolLib; using System.Collections; /// /// 这个类主要做了一件事,就是优化了,NGUI UIScrollView 在数据量很多都时候, /// 创建过多都GameObject对象,造成资源浪费. /// public class LoopScrollView : MonoBehaviour, ILoopScrollView { public enum ArrangeDirection { Left_to_Right, Right_to_Left, Up_to_Down, Down_to_Up, } /// /// items的排列方式 /// public ArrangeDirection arrangeDirection = ArrangeDirection.Up_to_Down; /// /// 列表单项模板 /// public GameObject itemPrefab; /// /// The items list. /// public List itemsList; /// /// The datas list. /// public List datasList; public UIScrollView scrollView; public GameObject itemParent; /// /// itemsList的第一个元素 /// LoopItemObject firstItem; /// /// itemsList的最后一个元素 /// LoopItemObject lastItem; /// /// 第一item的起始位置 /// public Vector3 itemStartPos = Vector3.zero; public DelegateHandler OnItemInit; /// /// 菜单项间隙 /// public float gapDis = 0f; private Vector3 m_TopLeft, m_BtmRight; private System.Action onDragFinish; /// /// 预制可见个数 会影响边缘的回弹spring /// public int m_maxViewCnt = 2; bool mTop = true; // 对象池 Queue itemLoop = new Queue(); void Awake() { if (itemPrefab == null || scrollView == null || itemParent == null) { Debug.LogError("LoopScrollView.Awake not set value in inspector!"); } if (m_maxViewCnt < 2) { Debug.LogError("Make sure your view cnt more than 2!"); } if (arrangeDirection == ArrangeDirection.Up_to_Down || arrangeDirection == ArrangeDirection.Down_to_Up) { scrollView.movement = UIScrollView.Movement.Vertical; } else { scrollView.movement = UIScrollView.Movement.Horizontal; } scrollView.onDragFinished = OnDragFinish; } void Start() { UIPanel panel = scrollView.panel; Vector4 v4 = scrollView.panel.baseClipRegion; Vector3 v3 = scrollView.transform.localPosition; v3 = new Vector3(v3.x + panel.clipOffset.x + v4.x, v3.y + panel.clipOffset.y + v4.y, v3.z); Vector3 topleft = new Vector3(v3.x - v4.z / 2, v3.y + v4.w / 2, v3.z); Vector3 btmright = new Vector3(v3.x + v4.z / 2, v3.y - v4.w / 2, v3.z); m_TopLeft = scrollView.transform.parent.localToWorldMatrix.MultiplyPoint(topleft); m_BtmRight = scrollView.transform.parent.localToWorldMatrix.MultiplyPoint(btmright); //XDebug.singleton.AddGreenLog("topleft =" + topleft + "btmright" + btmright, "m_TopLeft=" + m_TopLeft + "...." + "m_BtmRight" + m_BtmRight); } private void OnDragFinish() { if (lastItem != null && IsScrollLast()) { //XDebug.singleton.AddGreenLog("xxxxxxxxxxxxxxxxxxxxxxxxxx"); if (onDragFinish != null) onDragFinish(); } } void FixedUpdate() { Validate(); if (frameIndex <= 4) { frameIndex = frameIndex << 1; if (frameIndex == 1 << 2) { YiledScroll(); //第二帧调用 } } } private int frameIndex = 1 << 10; /// /// Init the specified datas. /// /// Datas. public void Init(List datas, DelegateHandler onItemInitCallback, System.Action dragFinish, int pivot = 0, bool forceRefreshPerTime = false) { mTop = pivot == 0; onDragFinish = dragFinish; if (forceRefreshPerTime) { RefeshPerTime(datas, onItemInitCallback, pivot); } else { if (IsEqual(datas)) { // Debug.Log("equal, do noting!"); } else if (IsAddNew(datas)) { AddItem(datas[datas.Count - 1]); } else if (UpdateNewOne(datas)) { UpdateItem(datas[datas.Count - 1]); } else { RefeshPerTime(datas, onItemInitCallback, pivot); } } frameIndex = 1; } private void RefeshPerTime(List datas,DelegateHandler onItemInitCallback,int pivot) { mTop = pivot == 0; this.OnItemInit = onItemInitCallback; Resetloop(); datasList = datas; Validate(); ResetScroll(); int cnt = Mathf.Min(m_maxViewCnt, datasList.Count); for (int i = 0; i < cnt; i++) Validate(); } void YiledScroll() { ResetScroll(); CheckBTM(); } private void Resetloop() { if (itemsList != null) { for (int i = 0; i < itemsList.Count; i++) { PutItemToLoop(itemsList[i]); } itemsList.Clear(); } if (datasList != null) datasList.Clear(); } private bool activable { get { return gameobject.activeSelf; } } public void SetPivot(bool istop){ } public GameObject GetTpl() { return itemPrefab; } private bool IsAllInvisible() { bool all_invisible = true; for (int i = 0; i < itemsList.Count; i++) { if (IsVisible(itemsList[i])) { all_invisible = false; break; } } return all_invisible; } /// /// 检验items的两端是否要补上或删除 /// private void Validate() { if (datasList == null || datasList.Count == 0) return; // 如果itemsList还不存在 if (itemsList == null || itemsList.Count == 0) { itemsList = new List(); LoopItemObject item = GetItemFromLoop(); if (mTop) InitItem(item, 0, datasList[0]); //默认起始0 else InitItem(item, datasList.Count - 1, datasList[datasList.Count - 1]); firstItem = lastItem = item; itemsList.Add(item); ResetScroll(); } if (IsAllInvisible()) return; // 先判断前端是否要增减 if (IsVisible(firstItem)) { // 判断要不要在它的前面补充一个item if (firstItem.dataIndex > 0) { LoopItemObject item = GetItemFromLoop(); int index = firstItem.dataIndex - 1; AddToFront(firstItem, item, index, datasList[index]); firstItem = item; itemsList.Insert(0, item); } } else { // 判断要不要将它移除 // 条件:自身是不可见的; 且它后一个item也是不可见的(或被被裁剪过半的).隐含条件itemsList.Count>=2. if (itemsList.Count > m_maxViewCnt && !IsVisible(itemsList[0]) && !IsVisible(itemsList[1])) { itemsList.Remove(firstItem); PutItemToLoop(firstItem); firstItem = itemsList[0]; } } // 再判断后端是否要增减 if (IsVisible(lastItem)) { // 判断要不要在它的后面补充一个item if (lastItem.dataIndex < datasList.Count - 1) { LoopItemObject item = GetItemFromLoop(); int index = lastItem.dataIndex + 1; AddToBack(lastItem, item, index, datasList[index]); lastItem = item; itemsList.Add(item); } } else { // 判断要不要将它移除 // 条件:自身是不可见的;且它前一个item也是不可见的(或被被裁剪过半的).隐含条件itemsList.Count>=2. if (itemsList.Count > m_maxViewCnt && !IsVisible(itemsList[itemsList.Count - 1]) && !IsVisible(itemsList[itemsList.Count - 2])) { itemsList.Remove(lastItem); PutItemToLoop(lastItem); lastItem = itemsList[itemsList.Count - 1]; } } } private bool isPivotLast { get { return lastItem != null && datasList != null && datasList.Count > 1 && lastItem.dataIndex == datasList.Count - 1; } } private bool IsEqual(List datas) { bool equal = true; if (datas != null && datasList != null && datas.Count == datasList.Count) { for (int i = 0; i < datasList.Count; i++) { if (datasList[i].LoopID != datas[i].LoopID) { equal = false; break; } } } else { equal = false; } return equal; } // 在列表最后更新一个 private bool UpdateNewOne(List datas) { bool isnew = true; if (datas != null && isPivotLast && datas.Count == datasList.Count ) { int iret = Mathf.Min(4, datasList.Count - 1); //只比较前四个是为了快速,比较新增和刷新整个list效果一样 for (int i = 0; i < iret; i++) { if (datasList[i + 1].LoopID != datas[i].LoopID) { isnew = false; break; } } } else { isnew = false; } return isnew; } // 在列表最后新增一个 private bool IsAddNew(List datas) { bool isnew = true; if (datas != null && isPivotLast && datas.Count - datasList.Count == 1) { int iret = Mathf.Min(4, datasList.Count); //只比较前四个是为了快速,比较新增和刷新整个list效果一样 for (int i = 0; i < iret; i++) { if (datasList[i].LoopID != datas[i].LoopID) { isnew = false; break; } } } else { isnew = false; } return isnew; } // 此方法主要是为了避免新加item而整体进行刷新 public void AddItem(LoopItemData data) { datasList.Add(data); LoopItemObject item = GetItemFromLoop(); int index = datasList.Count - 1; AddToBack(lastItem, item, index, datasList[index]); lastItem = item; itemsList.Add(item); } public void UpdateItem(LoopItemData data) { if (datasList.Count > 0) datasList.RemoveAt(0); if (itemsList.Contains(firstItem)) itemsList.Remove(firstItem); PutItemToLoop(firstItem); if (itemsList.Count > 0) firstItem = itemsList[0]; AddItem(data); } public bool IsVisible(LoopItemObject obj) { if (obj == null || obj.widget == null || obj.widget.transform == null) return false; return IsVisible(obj.widget.transform); } private bool IsVisible(Transform tran) { bool x = tran.position.x >= m_TopLeft.x && tran.position.x <= m_BtmRight.x; bool y = tran.position.y >= m_BtmRight.y && tran.position.y <= m_TopLeft.y; return x && y; } public GameObject gameobject { get { return this.gameObject; } } public bool IsScrollLast() { if (lastItem != null && lastItem.widget != null) { return IsVisible(lastItem); } else { return true; } } [ContextMenu("reset pos")] public void ResetScroll() { scrollView.ResetPosition(mTop ? 0f : 1f); } public void SetClipSize(Vector2 size) { Vector4 reg = scrollView.panel.baseClipRegion; reg.z = size.x; reg.w = size.y; scrollView.panel.baseClipRegion = reg; } public void SetDepth(int depth) { if (scrollView.panel != null) scrollView.panel.depth = depth; } public GameObject GetFirstItem() { return firstItem.GetObj(); } public GameObject GetLastItem() { return lastItem.GetObj(); } /// /// 构造一个 item 对象 /// LoopItemObject CreateItem() { GameObject go = NGUITools.AddChild(itemParent, itemPrefab); UIWidget widget = go.GetComponent(); LoopItemObject item = new LoopItemObject(); item.widget = widget; if(!go.activeSelf) go.SetActive(true); return item; } /// /// 用数据列表来初始化scrollview /// void InitItem(LoopItemObject item, int dataIndex, LoopItemData data) { item.dataIndex = dataIndex; if (OnItemInit != null) { OnItemInit(item as ILoopItemObject, data); } item.widget.transform.localPosition = itemStartPos; } /// /// 在itemsList前面补上一个item /// private void AddToFront(LoopItemObject priorItem, LoopItemObject newItem, int newIndex, LoopItemData newData) { if (!CheckItem(priorItem) || !CheckItem(newItem)) return; InitItem(newItem, newIndex, newData); // 计算新item的位置 if (scrollView.movement == UIScrollView.Movement.Vertical) { float offsetY = priorItem.widget.height * 0.5f + gapDis + newItem.widget.height * 0.5f; if (arrangeDirection == ArrangeDirection.Down_to_Up) offsetY *= -1f; newItem.widget.transform.localPosition = priorItem.widget.cachedTransform.localPosition + new Vector3(0f, offsetY, 0f); } else { float offsetX = priorItem.widget.width * 0.5f + gapDis + newItem.widget.width * 0.5f; if (arrangeDirection == ArrangeDirection.Right_to_Left) offsetX *= -1f; newItem.widget.transform.localPosition = priorItem.widget.cachedTransform.localPosition - new Vector3(offsetX, 0f, 0f); } } /// /// 在itemsList后面补上一个item /// private void AddToBack(LoopItemObject backItem, LoopItemObject newItem, int newIndex, LoopItemData newData) { if (!CheckItem(backItem) || !CheckItem(newItem)) return; InitItem(newItem, newIndex, newData); // 计算新item的位置 if (scrollView.movement == UIScrollView.Movement.Vertical) { float offsetY = backItem.widget.height * 0.5f + gapDis + newItem.widget.height * 0.5f; if (arrangeDirection == ArrangeDirection.Down_to_Up) offsetY *= -1f; newItem.widget.transform.localPosition = backItem.widget.cachedTransform.localPosition - new Vector3(0f, offsetY, 0f); } else { float offsetX = backItem.widget.width * 0.5f + gapDis + newItem.widget.width * 0.5f; if (arrangeDirection == ArrangeDirection.Right_to_Left) offsetX *= -1f; newItem.widget.transform.localPosition = backItem.widget.cachedTransform.localPosition + new Vector3(offsetX, 0f, 0f); } } private void CheckBTM() { if (mTop && lastItem != null && lastItem.widget != null && arrangeDirection == ArrangeDirection.Up_to_Down) { if (m_BtmRight.y > lastItem.widget.transform.position.y) { mTop = false; ResetScroll(); } } } #region 对象池性能相关 /// /// 从对象池中取行一个item /// /// The item from loop. LoopItemObject GetItemFromLoop() { LoopItemObject item = null; while (itemLoop.Count > 0 && !CheckItem(item)) { item = itemLoop.Dequeue(); } if (item == null) { item = CreateItem(); } if (CheckItem(item) && !item.widget.gameObject.activeSelf) item.widget.gameObject.SetActive(true); return item; } private bool CheckItem(LoopItemObject item) { return item != null && item.widget != null && item.widget.gameObject != null; } /// /// 将要移除的item放入对象池中 /// --这个里我保证这个对象池中存在的对象不超过3个 /// /// Item. void PutItemToLoop(LoopItemObject item) { if (!CheckItem(item)) return; if (itemLoop.Count >= 3) { DestroyImmediate(item.widget.gameObject); return; } item.dataIndex = -1; item.widget.gameObject.SetActive(false); itemLoop.Enqueue(item); } #endregion }