NGUI之UIGrid可循环复用可定位列表的实现
发表于2016-12-08
1、背景:
我们在项目中常需要使用到UIGrid,例如好友列表、排行榜、背包列表、武器列表等等。但是原始的UIGrid并没有实现循环复用的功能,且没有实现定位功能。但策划提示定位功能时,我们常是针对具体的需求写一个特殊的计算位置、定位的功能,没有通用性,也常常为了快速实现,没有实现循环复用的功能,最终导致体验效果不好,工作量大,维护困难等问题。
2、功能需求:
在做魂斗罗项目过程中,我总结一下,我们希望得到如下的一个列表,
1、上下拖动时,可向上对齐、中间对齐;
2、左右拖动时,可向左对齐 、中间对齐、向右对齐;
3、定位,无论是item创建时、还是创建完成后,我们希望能快速定位对某个item;
4、原位刷新 ;
5、每帧创建一个;
6、创建在UIScrollView的item数量尽量的少,循环复用;
7、可以实现瞄点;
8、使用方便。
我们针对这些要求,实现了UIGrid的改造版UICycleGrid,实现了如上的内容。但本文主要讲述两点循环复用和定位这两个功能。
3、实现循环复用的原理:
首先创建一屏多一点的Item,然后把它的Transform和当前的位置下标数据保存在m_itemList 列表中,注册UIPanel.onClipMove回调,当列表拖动里,则对m_itemList 列表的数量进行比较,把屏幕外的item找出来,把需要在屏幕内显示的item的却没有在m_itemList列表里也找出来,此时把屏幕外的item数量刷新成屏幕内的,同时更新m_itemList里的数据。如下图。
protected List m_itemList = new List();protected class ItemData{ public int m_index = 0; public Transform m_Trans; public ItemData(int index, Transform trans) { m_index = index; m_Trans = trans; }}/// /// UIPanel拖动了/// /// protected virtual void OnMove(UIPanel panel){ WrapContent();}/// /// 循环内容/// [ContextMenu("循环内容")]protected void WrapContent(bool bIgnore = false){ if (!m_InitDone) { return; } int min = 0, max = 0; GetMinMax(out min, out max); if ((min < -m_MaxPerLine || max > m_MaxCount + m_MaxPerLine) && !bIgnore) { // Logger.Log("WrapContent maxCnt:" + m_MaxCount); return; } List exItemList = new List(); List hasItemList = new List(); for (int i = 0; i < m_itemList.Count; i++) { ItemData itemData = m_itemList[i]; if (itemData.m_index < min || itemData.m_index >= max) { exItemList.Add(itemData); } else { hasItemList.Add(itemData); } } List<int> noHasItem = new List<int>(); for (int i = min; i < max; i++) { bool bFind = false; for (int j = hasItemList.Count - 1; j >= 0; j--) { if (i == hasItemList[j].m_index) { bFind = true; break; } } if (!bFind) { noHasItem.Add(i); } } if (noHasItem.Count != exItemList.Count) { Logger.LogError("noHasItem.Count != exItemList.Count"); } for (int i = 0; i < noHasItem.Count && i < exItemList.Count; i++) { ItemData itemData = exItemList[i]; int index = noHasItem[i]; SetItemPos(index, itemData.m_Trans); OnItemCallBack(index, itemData.m_Trans); itemData.m_index = index; } for (int i = 0; i < hasItemList.Count; i++) { ItemData itemData = hasItemList[i]; SetItemPos(itemData.m_index, itemData.m_Trans); if(bIgnore) { OnItemCallBack(itemData.m_index, itemData.m_Trans); } } m_lastClipOffset = m_UIPanel.clipOffset;}
4、定位功能的实现:
首先根据定位到某个item的index,计算出m_UIPanel.clipOffset和 m_UIPanel.transform.localPosition的偏移。然后采用UIPanel拖动时的item位置更新原理,把屏幕外的多余的item补全屏幕内缺失的item。更新m_itemList列表数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | /// /// 定位到某个index的ITEM /// /// public void GoToIndex(int index) { if (!m_InitDone) { ContinueToCreateItem(m_MaxCount, index); return; } GoToIndexPrivate(index);//设置UIPanel的位置偏移 WrapContent(true);//把非屏幕内的item移到屏幕内里 } private void GoToIndexPrivate(int index) { m_ScrollView.DisableSpring(); float startIndex = index; float endIndex = 0; float viewX = 0; float viewY = 0; m_vecViewSize = m_UIPanel.GetViewSize(); viewX = m_vecViewSize.x / m_ItemWidth; viewY = m_vecViewSize.y / m_ItemHeight; float viewCnt = 0; if (m_DragDirection == DragDirection.UpDown) { viewCnt = viewY * m_MaxPerLine; } else { viewCnt = viewX * m_MaxPerLine; } if (m_MaxCount > viewCnt) { endIndex = m_MaxCount - viewCnt; } startIndex = Mathf.Clamp(startIndex, 0, endIndex); if (m_DragDirection == DragDirection.UpDown) { float offsetY = (float)(startIndex / m_MaxPerLine) * m_ItemHeight; m_UIPanel.clipOffset = new Vector2(0, -offsetY); // Logger.Log("clipOffset:" + m_UIPanel.clipOffset + " endIndex:" + endIndex + " startIndex:" + startIndex + " goIndex:" + index); m_UIPanel.transform.localPosition = new Vector3(0, offsetY, 0); } else { float offsetX = (float)(startIndex / m_MaxPerLine) * m_ItemWidth; m_UIPanel.clipOffset = new Vector2(offsetX, 0); // Logger.Log("clipOffset:" + m_UIPanel.clipOffset); m_UIPanel.transform.localPosition = new Vector3(-offsetX, 0, 0); } } |
5、性能分析:
本文的Grid的创建过程中是分帧创建,可以N帧创建1个。在拖动列表时,只会刷新需求显示在屏幕内却没有拖动后没有显示在屏幕的部分,所以拖动列表的性能消耗还是很小的。
6、总结:
终于可以面对策划的定位需求再也不怕了,面对grid性能问题也松了一口气,好开心。还有很多实现的细节,不能一一讲述,把源码附上,欢迎拍砖。
