NGUI之UIGrid可循环复用可定位列表的实现

发表于2016-12-08
评论13 3.9k浏览
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性能问题也松了一口气,好开心。还有很多实现的细节,不能一一讲述,把源码附上,欢迎拍砖。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论