Monday, 9 December 2013

"Infinite Scrolling" for Unity3D Using NGUI's UIScrollView (1st attempt)

For latest update please check this post instead
I've been using NGUI for UI related work on my Unity project for the past few weeks and it is a breath of fresh air when compared to the stock UnityGUI (OnGUI()).

Part of my project relate to display a relatively large amount of data dynamically (well not very large but in thousands) and instantiating a prefab for each data element didn't seem like a good idea to me :-)

So instead I decided to add some logic to do a scroll view with fixed pool of items that I reuse and position according to the direction of the scroll and get it fed with the correct data.

Although I am sure that there are existing solutions I decided to do my own.

The logic so far is built using UIGrid with fixed cell height for the moment (not well suited for UITable with different cell height) and the panel is using soft clip. the Scroll view is using momentum only (Spring physics breaks my current logic for some reason)

Initialization steps:
  1. Pool size is calculated using the cell height against the panel height plus some buffer (currently I am using a buffer of 4 list items)
  2. List items' pool is instantiated with corresponding data from the data list
  3. A map is maintained to keep track of which data item are used in which list items
While Scrolling:
  1. Any item that turns a visible from an invisible state will notify the main controller
  2. We check the direction of the scroll based on which item is visible (e.g. if the next item is visible means that we are dragging upwards)
  3. based on the direction we reuse the items at the top or the bottom of the list accordingly (e.g. if direction is up the top item moves to the bottom and get populated with data from the corresponding data element)

This is a first attempt and further posts will follow as the logic evolve.

==UPDATE==
I am in the process of making this component available as open source.
Meanwhile we've launched a free app on Android that uses it called Avatar Messenger
==UPDATE 2==
 The component is available as open source
https://github.com/yaseralhaidery/InfiniteList_NGUI

22 comments:

  1. its just what I´m trying to do. Nice post Ill check out further post

    ReplyDelete
  2. Thanks, it's exactly what I need. But can you explain how do you change the order of items while scrolling? Do you actualy change it's position or it is done using sorting by name?

    ReplyDelete
    Replies
    1. Hi,

      I am planning to make another detailed post once I have the time as I do now have a more robust solution.

      Did you mean item index in the data list? then no I don't change the order

      In short i have two lists: one is the data list in whatever order you would want which I reference ( item index is not changed in this list) and the other is items pool list which keep track of data item index its representing and I position it in the table according to that index. hope this is clear

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

      Delete
    3. Thanks for your reply! Some more questions:

      1."...Any item that turns a visible from an invisible state will notify the main controller"
      How do you detect changing of items visible state? I tried OnEnamle() method, but it doesn't work

      2 "...based on the direction we reuse the items at the top or the bottom of the list accordingly (e.g. if direction is up the top item moves to the bottom and get populated with data from the corresponding data element)"

      How do you detect current top/bottom item and move it to bottom/top of the UIGrid? I was looking thrue NGUI documentation, but i still can't find an answer.

      I would appreciate your help, because I'm really stuck at this two points

      Delete
    4. 1. for that I am checking using UIPanel IsVisible() method

      2. There are two ways to check for scrolling direction: you can either check against scrollview currentMomentum property (y value in this case)... or you can check against the current item that became invisible (I am doing this at the moment): if the item above the invisible item is visible then scrolling down and vice versa

      Delete
    5. and the current top and bottom items will corresponds to your pool of objects.

      Delete
  3. Thanks for that! Direction is not where i'm stuck. The point is, how exactly to get the top and bottom items, which I'll need to move? What method do you use to get those gameobjects?

    Do you move the top item to the bottom using transform.position and after that calling Reposition() method of UIGrid? I guess, there is some other way to do it, but I still don't get it.

    Please, excuse me for being annoying:)

    ReplyDelete
    Replies
    1. No I don't use Reposition method in this case. I just change the transform position of the top and bottom items (either subtract cellheight * poolSize or add cellheight* poolsize depending on which item you need to move (top or bottom))

      I keep track of all gameobjects (the pool items) using a hashmap of indexes (data index as a key and a gameobject index as a value) which i keep updating when repositioning the items to represent new data...

      Delete
    2. Thank you very much! I'll try to do that.
      Have you try to use NData to provide automatic data binding in the infinite scrolling table? If so, could you give any example of that?

      Delete
  4. Tell me please, how do you avoid inaccuracy in positioning elements while scrolling? I mean, for example, when I move the top item to the bottom while fast scrolling the inaccuracy in y coordinate appears and every time it becomes bigger.

    Can you post your source code? It would be really helpful!

    ReplyDelete
    Replies
    1. sorry for my late reply as I didnt notice your comment. as I mentioned below I can't share the full source(for now) but I will update with some snippets when I get the chance

      Delete
  5. Replies
    1. I am looking into open sourcing the component and releasing it in the asset store for free so people can update it as they wish.. need to clear some legal stuff with my employer first since I developed the component while making them an app instead of using my free time. But before that I will update the blog with more info on how I did it and some snippets

      Delete
    2. I have hard trouble with scrolling. I use uiscrollview + uigrid, so i dynamic fill grid. If count items < 50 all work quick, after 100 items i see very hard freeze and memory leaks, i use OnBecameInvisible and OnBecameVisible for all items. If i scroll CPU say me WTF 0_0 ))) So can you send you source\sample to marmentyev@gmail.com I would be most grateful.

      Delete
    3. I will let you know once the code in GitHub which should be soon enough ( I will make a post with the code as well) in the meantime make sure to disable the colliders for invisible items

      Delete
  6. Hope post sources or some snippets. Meet same problem in my card game, create more than 300 prefabs in one frame cause 2 secs freeze... Also want to known how to set scrollbar correct in your way.

    ReplyDelete
    Replies
    1. Hi Brian,
      I've just managed to make the component code as open source in github:
      https://github.com/OrangeLabsUK/InfiniteList_NGUI
      At the moment I don't support full NGUI scroll bar (I made a scroll indicator i.e. not interactive)

      Delete
  7. hey I am getting this error when i am runninig infinite list demo scene

    transform.localPosition assign attempt for 'ScrollIndicator' is not valid. Input localPosition is { 0.568161, NaN, 0.000000 }.
    UnityEngine.Transform:set_localPosition(Vector3)
    InfiniteListPopulator:Update() (at Assets/InfiniteListDemo/InfiniteListPopulator.cs:146)

    ReplyDelete