Monday 7 April 2014

"Infinite Scrolling" Using Unity3d and NGUI's UIScrollview Part 2

Previously I posted a demo of an early version of NGUI UIScrollview that uses a pool of objects to display large amount of data. e.g. contacts list, achievements...etc

Since then I updated the component with more features such as:
  • Better scroll handling which allowed me to use spring physics without issues so far
  • Sections support (not as sophisticated as the indexpath in iOS but did the job for now)
  • Basic scroll indicator (i.e. not interactive)
  • Made for on item per row for now...(uses UITable... should work with UIGrid)
  • Only fixed row height is supported.
 Here is a demo of the result:

How does the infinite scrolling work?
1. Each visible item will notify the main controller when it becomes invisible using NGUI's panel function... here is an example of how I've done it:
private bool isVisible;
void CheckVisibilty() 
{
 bool currentVisibilty = panel.IsVisible(backgroundSprite);
 if(currentVisibilty != isVisible)
 {
  isVisible = currentVisibilty;
  thisCollider.enabled = isVisible;
   
  if(!isVisible)
  {
   // notify main controller. itemNumber is the index of the pool item (not the data)
   StartCoroutine(listPopulator.ItemIsInvisible(itemNumber));
  }
 }
}
2. On receiving this notification the main controller do the following:
  • Retrieve the data index from the pool item
  • Check the visiblity of the next or previous item to determine scroll direction. (I didn't use the currentMomentum of UIScrollview... )
  • Based on the direction we start moving items of the pool from the top to the bottom (or vice versa) and change it to an item or section...
public IEnumerator ItemIsInvisible(int itemNumber)
{
    if(isUpdatingList) yield return null;
    isUpdatingList = true;
    if(dataList.Count > poolSize)// we need to do something "smart"... 
    {
 Transform item = itemsPool[itemNumber];
 int itemDataIndex = 0;
 if(item.tag.Equals(listItemTag))
            itemDataIndex = item.GetComponent().itemDataIndex;
 if(item.tag.Equals(listSectionTag))
     itemDataIndex = item.GetComponent().itemDataIndex;

 int indexToCheck=0;
 InfiniteItemBehavior infItem = null;
 InfiniteSectionBehavior infSection = null;
 if(dataTracker.ContainsKey(itemDataIndex+1))
 {
     infItem = itemsPool[(int)(dataTracker[itemDataIndex+1])].GetComponent();
     infSection = itemsPool[(int)(dataTracker[itemDataIndex+1])].GetComponent();

     if((infItem != null && infItem.verifyVisibility()) || (infSection != null && infSection.verifyVisibility()))
     {
  // dragging upwards (scrolling down)
  indexToCheck = itemDataIndex -(extraBuffer/2);
  if(dataTracker.ContainsKey(indexToCheck))
  {
      //do we have an extra item(s) as well?
      for(int i = indexToCheck; i>=0; i--)
      {
          if(dataTracker.ContainsKey(i))
   {
       infItem = itemsPool[(int)(dataTracker[i])].GetComponent();
       infSection = itemsPool[(int)(dataTracker[i])].GetComponent();
       if((infItem != null && !infItem.verifyVisibility()) || (infSection != null && !infSection.verifyVisibility()))
       {
    item = itemsPool[(int)(dataTracker[i])];
    if((i)+poolSize < dataList.Count && i>-1)
    {
        // is it a section index?
        if(sectionsIndices.Contains(i+poolSize))
        {
     // change item to section
     ChangeItemToSection(item,i+poolSize,i);
        }
        else if(item.tag.Equals(listSectionTag))
        {
     // change section to item
     ChangeSectionToItem(item,i+poolSize,i);
        }
        else
        {
     PrepareListItemWithIndex(item,i+poolSize,i);
        }
    }
       }
   }
   else
   {
     scrollCursor = itemDataIndex-1;
     break;
   }
      }
        }
           }
       }
       if(dataTracker.ContainsKey(itemDataIndex-1))
       {
    infItem = itemsPool[(int)(dataTracker[itemDataIndex-1])].GetComponent();
    infSection = itemsPool[(int)(dataTracker[itemDataIndex-1])].GetComponent();

    if((infItem != null && infItem.verifyVisibility()) || (infSection != null && infSection.verifyVisibility()))
    {
        //dragging downwards check the item below
        indexToCheck = itemDataIndex +(extraBuffer/2);
    
        if(dataTracker.ContainsKey(indexToCheck))
        {
            // if we have an extra item
     for(int i = indexToCheck; i();
      infSection = itemsPool[(int)(dataTracker[i])].GetComponent();
      if((infItem != null && !infItem.verifyVisibility()) || (infSection != null && !infSection.verifyVisibility()))
      {
          item = itemsPool[(int)(dataTracker[i])];
          if((i)-poolSize > -1 && (i) < dataList.Count)
          {
       // is it a section index?
       if(sectionsIndices.Contains(i-poolSize))
       {
           // change item to section
           ChangeItemToSection(item,i-poolSize,i);
       }
       else if(item.tag.Equals(listSectionTag))
       {
           // change section to item
           ChangeSectionToItem(item,i-poolSize,i);
       }
       else
       {
           PrepareListItemWithIndex(item,i-poolSize,i);
       }
          }
             }
                }
                else
                {
             scrollCursor = itemDataIndex+1;
             break;
                }
            }
               }
           }
        }
     }
     isUpdatingList = false;
}
The full source with sample and documentation is available on Github under MIT License :

Free Android app called Avatar Messenger is using this component for the phone book list: https://play.google.com/store/apps/details?id=com.orange.labs.avachat

4 comments:

  1. Thanks for sharing this video, Yaser!

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  3. Thanks it will help me out :)
    One question. Your code supports only fixed height?
    There is a comment saying "fixed now.." Will you update it with dynamic height with UITable?

    ReplyDelete