OSA Demo: Multiple Prefabs for Different Item Types
Create scroll views with different item types using OSA’s multiple prefabs feature. Perfect for chat systems, feeds, and mixed-content lists.
Contents
- Description
- Scene
- Code
- Description
This doc refers to the simple_multiple_prefabs scene. The multiple_prefabs has a bit too much non-essential stuff in it to be used for learning the concept.
Having multiple prefabs is indeed necessary sometimes. The way you could achieve that in OSA is by having a base class for the ViewsHolder and for the model, and implementations of these classes that correspond to your different types of items. When creating the ViewsHolder, you’ll return a new instance of the corresponding type. Also, the models list will now contain mixed model types and when retrieving them you’ll cast those models to the corresponding type.
We have 3 prefabs:
- “Green”: contains a text body
- “Orange”: contains a Unity Slider displaying a float value
- “Ad”: advertisement. It contains a variable-sized, RawImage where we load a local texture, and it’s clickable.
The decision to also include this 3rd prefab with variable size is because several people have asked for it. Except this, everything else is as simple as possible: you can only set the items count, and we’re using SimpleDataHelper.
- Scene
[Screenshot - see Unity Asset Store documentation for visual guide]
Same general structure as for any list ScrollView:
[Screenshot - see Unity Asset Store documentation for visual guide]
The prefabs are put under Prefabs in hierarchy for organizational purpose. They are assigned to our custom adapter’s parameters in inspector. These are used at runtime to instantiate the item views based on your data.
- Code
[Screenshot - see Unity Asset Store documentation for visual guide]
The SimpleMultiplePrefabsSceneEntry ties some buttons in the Drawer to the adapter.
The most important event is the OnItemCountChangeRequested, which is called when you press Set count. Here, we’re just setting a new item count and the SimpleDataHelper notifies the adapter about it. Before doing so, we’re generating all of the models. We’re randomly choosing between the Green and Orange models, but every 20th item is an Ad model. The ad models have a random string id and a random texture from the ones provided in the _AdTextures field (exposed in inspector).
Any item’s initial size is given by the _Params.DefaultItemSize, but we’ll see how the Ad item can not only override that, but have a variable size for different Ad models.
Now open the SimpleMultiplePrefabsExample script and let’s take a look. There’s no ideal order of explaining the concepts, because some of them refer to each other and going through them linearly (top to bottom) should be done at least 2 times.
Fields
Data is a SimpleDataHelper, which is simply a wrapper around a List and notifies the adapter on each change you do. This one needs to be initialized before the adapter, so we’re doing it in Start(), before calling base.Start().
Inner classes
MyParams is pretty basic: it contains the 3 prefabs for our data types. The AssertValidWidthHeight call you see is a sanity check that’s recommended, because having an invalid size can lead to hard-to-diagnose bugs.
We’ll come back to SimpleMultiplePrefabsExample after we’ll go over the models and the ViewsHolders.
Let’s explore the files under /SimpleMultiplePrefabs/Models.
Model classes
SimpleBaseModel is the base class for all the 3 models. CachedType is a property we’ll need later
GreenModel and OrangeModels are so simple that they don’t deserve any attention.
AdModel contains a string adID which is just used to display a console log when the ad is clicked. The adTexture field is assigned when the models are created - this is the texture that’ll be displayed in the ad item.
Now let’s see what’s under /SimpleMultiplePrefabs/ViewHolders.
ViewHolder classes
You’ll see a 1:1 correspondence between the models and ViewsHolders. There are 4 model classes, including the base, so there are 4 ViewsHolder classes.
SimpleBaseVH defines a titleText which the items may or may not have. The CanPresentModelType() is abstract so inheritors will specify exactly which models they can present. UpdateViews() does the main job: tying the model’s data to the view. UpdateSize() gives each ViewsHolder a chance to manually change the item’s RectTransform, if it needs to.
GreenVH adds a contentText which is used to display the GreenModel’s text. You can see its CanPresentModelType() returns true only for GreenModels. In UpdateViews() it casts the received model to the type it knows it’ll receive and it updates the contentText accordingly.
OrangeVH is conceptually identical to GreenVH.
AdVH needs a RawImage to display its texture. The choice for a RawImage instead of an Image was made because you can adapt this to images downloaded from the internet, which come in the Texture format, not Sprite (which an Image component requires). You can see the Clicked event is a convenient wrapper around the ad button’s onClickListener. This will be used later. As the rest is the same, I’ll only explain the UpdateSize(): Here we’re calculating the texture’s aspect ratio(width over height) in order to decide which height to give to our item. The item’s current width was already decided by the adapter, based on the viewport’s width. You’ll see when UpdateSize() is called in a while.
Now let’s head back to SimpleMultiplePrefabsExample where all of these are nicely stitched.
OSA overrides
SimpleMultiplePrefabsExample.CreateViewsHolder():
In a regular list, we’ll know which VH and which prefab to use directly, since there’s only one. But because there are 3, we need to get the model at itemIndex, check for its CachedType and decide which of the 3 possible classes to use for the requested views holder, along with their corresponding prefabs from _Params. For the ad ViewsHolder, we’re also subscribing to its AdVH.Clicked event to simply display a log message in the console when the ad is clicked.
SimpleMultiplePrefabsExample.UpdateViewsHolder():
Retrieving the model at the needed index and passing it to the views to update them. We don’t know which model/views holder type is updated - everything’s nicely abstracted through inheritance.
An important call is the one to ScheduleComputeVisibilityTwinPass(). This is something explained in the CSF manual, but what it basically does is requesting the adapter to call our UpdateItemSizeOnTwinPass() after the current pass, for each visible item, then re-compute all the visible items’ positions and sizes. This is needed for our Ad model, which has non-default size
SimpleMultiplePrefabsExample.UpdateItemSizeOnTwinPass():
As previously explained for the AdVH class, we give the ViewsHolders a chance to update their own size to whatever they want, if they want. We’re on a vertical ScrollView, so the size refers to height - we thus return the RectTransform’s height.
SimpleMultiplePrefabsExample.IsRecyclable():
This is central to the multiple prefabs case. OSA calls this method on each of the ViewsHolder in its internal pool (i.e. ViewsHolders that aren’t used and are potentially recyclable). Knowing indexOfItemThatWillBecomeVisible, we retrieve the model at that index and see if it can be “presented” by the supplied candidate ViewsHolder.
Summary
For each item type, there’s a model class, a ViewsHolder class and a prefab
When OSA requests us to create a ViewsHolder, we’re deciding its type based on the type of the model stored at that index
Since we abstracted the item types under their own classes, the adapter itself is kept pretty clean. Only the ViewsHolder creation is using the concrete model types because for the purposes of this example, it’s less code to write than what would be needed to use a “factory” class that’d generate those for us using an “ItemType” enum.
Items can decide their own size, which AdVH does
The SimpleDataHelper wraps the list where we store our models. The Data list is of type List<SimpleBaseModel> and it contains models of different types. Since we decide whether a ViewsHolder can present a model at a specified index in SimpleMultiplePrefabsExample.IsRecyclable(), we know that in SimpleMultiplePrefabsExample.UpdateViewsHolder() the received ViewsHolder’s ItemIndex points to a valid model, compatible with it, so we retrieve it from the Data list and pass it to its UpdateViewS() method knowing that each specific ViewsHolder implementation will handle it internally
