What’s InfiniteScroll UI?

InfiniteScroll UI is a script extension that enables you to use the new ScrollRect control as an infinite spinner. You can load a collection of objects to its datasource and it will automatically show the objects while scrolling. This is the basics. You can also use databinding, a la MVVM, and all the changes in your collection or objects will get immediately reflected in your screen without having to manipulate the UI. The library comes with some specific classes to accomplish that: Bindable & ObservableCollection.

What can you achieve with this control?

The best I can do is to show you some videos for you to see what you can achieve. In fact, this control was born while I was creating my first Unity3d app called Monk as I needed to create some kind of spinners to select the options. In fact, all the selectors and the central bar with the notes use this control.

You can also take a look at this Unity3d Web Player example.

Bindable

The library has a light MVVM system that you can use not only for the InfiniteScroll but for other purposes. It relies in the Bindable class. The basic idea of this class is to be able to bind changes happening in your models to your UI in a encapsulated way. The Bindable object doesn’t know “who” is the model and only deals with model and interface changes.

Bear in mind that all this binding framework relies ultimately in the INotifyPropertyChanged interface. If your models implement this interface you will get notifications on every property change. Besides that, InfiniteScroll UI library comes with an ObservableCollection implementation for Mono, so you can update items inside collections and see its result immediately in the UI.

How does it work?

Once you’ve created your model and implemented INotifyPropertyChanged you’re ready to create your first Bindable object.

Every Bindable object has a DataContext property containing the model. Bindable is a generic class so, in order to create your Bindable object, you will have to inherit from Bindable<T> and use your model’s type as the template type.


//this is a base class that will help us implement INotifyPropertyChanged
public class PropertyChangedBase: INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

//model implementing INotifyPropertyChanged
public class MyModel : PropertyChangedBase
{

    #region [Name]

        public const string NAME = "Name";
        private string _name;
        public string Name {
            get{ return _name;}
            set {
                if (_name == value)return;
                _name = value;
                OnPropertyChanged (NAME);
            }
        }

    #endregion

    #region [Sprite]

        public const string SPRITE = "Sprite";
        private Sprite _sprite;
        public Sprite Sprite {
            get{ return _sprite;}
            set {
                if (_sprite == value)return;
                _sprite = value;
                OnPropertyChanged (SPRITE);
            }
        }

     #endregion

    public MyModel(string name, Sprite sprite){
            _name = name;
            _sprite = sprite;
    }

}

//Bindable class that will manage model changes.
public class MyBinder: Bindable<MyModel>
{

    public Image SourceImage;
    public Text Name;

    public void SetBindingName()
    {
        SourceImage.sprite= DataContext.Sprite;
        name = DataContext.Name;
        Name.text = DataContext.Name;
    }
}

As you can see in the code above, I’ve written a little base class to help you implement easily the INotifyPropertyChanged interface.

Bindable class runs under a few conventions:

  • Void methods starting with “SetBinding” and followed by the name of a Property will get automatically invoked everytime the value of the property changes, or more precisely, whenever the OnPropertyChanged method is invoked (if you have correctly implemented the INotifyPropertyChanged interface these will be equivalent).
  • The casing of the method is very important so if your property name is “age“, you will have to name your method “SetBindingage“. Otherwise it won’t work.
  • It’s also important to note that all the methods starting with “SetBindingwill be triggered everytime the DataContext property is setThis can be useful when we need to do something but only every time the DataContext property is set. I use to create a method called SetBindingAll (All is not a property, if fact it can’t exist a property with that name if we want it to work that way) and initialize things there.
  • If you want to create a Binding method for a property of a inner object you can. Imagine you have an object with another object which implements the INotifyPropertyChanged interface and you want to get notified. All you have to do is follow the standard convention but instead of the name of the property you have to put the name of the property containing the object (in your main object) + “_” + the name of the property you want to bind to; i.e. SetBindingMyInnerObject_Name. Try not to use underscores in your model properties unless necessary.
  • If you raise a PropertyChanged of a property called “all” all SetBinding methods will be invoked as if the datasource have been set. Imagine, in our code above, that our model’s Sprite property invokes OnPropertyChanged(“all”) instead of OnPropertyChanged(SPRITE). This would execute SetBindingName in our Binder.
  • SetBinding methods ended with “Command”, unless other SetBindingMethods,  will only get executed once the property value has changed but not when DataContext property is set.

ObservableCollection

Using ObservableCollection (which is a generic class) instead of List will give you the ability to be notified whenever the items in the collection are changed. This can come in handy in several situations. It implements an interface called INotifyCollectionChanged.

InfiniteScroll

The InfiniteScroll script must be attached to a ScrollRect with Movement Type set to Unrestricted and only Horizontal or Vertical checked. We want the content to run free but not in every direction.

Scroll Rect with unrestricted movement

The InfiniteScroll script has a reference to the Content of the ScrollRect and another one to a Prefab used to create the content elements.

infinitescroll_02

Let’s review all the properties:

  • Reposition Speed: The speed that the scroll uses when it detects a selection. The selection can be invoked by a click or by a deceleration. Default is 10.
  • Item Width: The width of the objects inside the scroll.
  • Item Height: The height of the objects inside the scroll.
  • Scroll Move Sensivity: This is a threshold used to detect motion of the scroll content. The higher it is the harder is to consider a movement a movement. It’s mainly used when a item is touched to avoid subtle motion to be detected as a movement.
  • Item Offset: When using positive values the items will be moved up (horizontal scroll) or right (vertical scroll). It allows you to adjust the items’ position.
  • Content Offset: Similar to the Item Offset but this time when using positive values the content will move right (horizontal scroll) and up(vertical scroll). Take into account that moving this parameter can cause that your content is not being properly shown.
  • Selection Offset: By default the central item is the one which is considered to be selected. If you want to change this behaviour then you should use this property to move the selected item right or left on the horizontal scrolls and up and down on the vertical ones. i.e. if you have a horizontal scroll with 3 elements and you want the left one to be the selection then you should put a -1 in this parameter. If you put a 1 then the right one will be the selection.
  • Is Vertical: Vertical or Horizontall scroll. Remember to set it too in the ScrollRect.
  • Invert Order: Inverts the order of the datasource.
  • Keep 1Scale For Items: Removed in last version.
  • Refresh Equality Comparer On DataSource Set: The first time a datasource is set the control will look for the IEquatable interface in your item (model) class. If it finds it, it will use it to decide which is the selected item. If not, it will use IndexOf method (from List class) . In order to avoid overloading everytime you make a selection this parameter is set to false. If you foresee that you’re not going to change the datasource and the datasource items’ class then this will work good for you. On the other hand, if you know that your datasource item’s class will change (first you fill it with apples and then with pears) then you will have to set this flag to true.
  • Content: The content object that will hold all the elements. This object is mandatory and the script won’t work without it.
  • Item Prefab: The prefab representing each of the objects/elements. This object is mandatory and the script won’t work without it.

InfiniteScroll API

There are a few more properties and methods that are only accessible programmatically:

  • DataSource: IEnumerable property that will load a collection into the InfiniteScroll object.
  • SelectedIndex: You can get the index of the selected item. If you set this property the selected item will change accordingly.
  • SelectedItem: You can get the selected item or set it, changing the UI accordingly.
  • SelectedPrefab: You can get the selected prefab or set it, changing the UI accordingly.
  • ScrollVelocity: You can get the velocity of the scroll movement.
  • ScrollVelocitySign: You can get the sign of the scroll movement’s velocity.
  • OriginalScrollVelocity: You can get the original velocity of the inner ScrollRect. ScrollVelocity is calculated by InfiniteScroll algorithm and may not be the same velocity as the OriginalScrollVelocity.
  • Next: This method will move the collection forward. It’s overloaded so you can pass a uint as a parameter and move the collection some steps forward.
  • Previous: This method will move the collection backward. It’s overloaded so you can pass a uint as a parameter and move the collection some steps back.
  • Move: This method will move the collection. It accepts an integer as a parameter. If you pass a negative value it will have the same effect as calling Previous. This method is very useful if you need to have +1 steps and want to control the movement by Editor handlers as Previous and Next are not available there in its overloaded form (because of the uint parameter).

InfiniteScrollItem

You may be still wondering how to put all this together and I will try to explain it to you right now.

Once you have your ScrollRect with an InfiniteScroll attached and ready, you’re almost there. But first you will need to decide how the scroll elements are going to be presented and create a prefab which will be used as a template. You can make this as complicated as you may need: you can use the prefab to load another one depending on the model’s properties, for example.

The prefab needs to have a custom InfiniteScrollItem script.  The InfiniteScrollItem will do the binding between your models and the UI (prefab). InfiniteScrollItem extends Bindable class and adds click events to it so all you have to do is extend InfiniteScrollItem as we did previously with Bindable class to create our Binder class.

Then remember to use the conventions I have exposed in previous paragraphs and create the SetBinding methods you may need. Remember that you will have a DataContext property containing the model so if you need to do two-way databind you can do it here, too.

InfiniteScrollSelectionChangedEvent

InfiniteScroll exposes an event that is triggered every time a selection is made helping you to catch it and react to every scroll selection change.

Where can I find it?

You can check the Asset Store here.