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性能问题也松了一口气,好开心。还有很多实现的细节,不能一一讲述,把源码附上,欢迎拍砖。