OSA 6.0-7.0 Complete Manual - Getting Started with Optimized ScrollView Adapter
This is the complete official manual for OSA (Optimized ScrollView Adapter) versions 6.0-7.0. This comprehensive guide covers everything from installation to advanced implementation.
Contents
- Documentation
- External links
- OSA wizard
- Usage
- Implementation
- Grid
- Table
- Example scenes & utilities (separate manual)
- Playmaker support
- Known issues, workarounds
- Tips
- FAQ
- Documentation
đ©Non-english speakers: Install Translate+ for Google Docs. It works surprisingly well!
If looking for the manual for v5.0, see here.
For v3.0 and higher, thereâs a quick start video guide on YouTube, which can be accessed via âTools->OSA->Quick start videoâ. This video is still relevant even for v4.0-v4.3, although there are a few differences which are pointed out in the migration guide
Starting with v4.3, OSA uses assembly definitions, so you can add a reference to the /Core/Scripts/OSA.asmdef if you also use them.
- External links
Migrate from 3.2 to 4.0-4.1, from 4.1 to 4.2, from 4.2 to 4.3, from 4.3 to 5.0, from 5.0 to 5.1, or from 5.x to 6.0. Note that migrations need to be done incrementally. For ex., you canât directly jump from 4.1 to 4.3.
- OSA wizard
Note: Youâll see SRIA instead of OSA in some screenshots, theyâre just from an older version.
Starting with v3.2, a graphical interface is provided to help you generate a ScrollView from scratch, a scrollbar and an OSA implementation based on 2 available templates: List or Grid.
See the workflow below
Create from scratch:
Choose horizontal or vertical and hit Create:
[Screenshot - see Unity Asset Store documentation for visual guide]
If you already have a ScrollRect in the scene, click here to open the âImplement OSAâ window:
[Screenshot - see Unity Asset Store documentation for visual guide]
Choose whether to use a scrollbar and which existing implementation to use (or from which template to generate a new one). If a scrollbar already exists, itâll be detected & linked automatically (you can still generate a new one and disable the old one, if you want):
[Screenshot - see Unity Asset Store documentation for visual guide]
The scrollbar can be Left/Right or Top/Bottom, depending on the ScrollViewâs orientation:
[Screenshot - see Unity Asset Store documentation for visual guide]
Initially, thereâs no implementation that can be used in production, only example implementations. Click here to create a new one:
[Screenshot - see Unity Asset Store documentation for visual guide]
Type a unique name and hit Generate:
[Screenshot - see Unity Asset Store documentation for visual guide]
The newly generated implementation will be auto-selected and, if a prefab property is detected on the used Params class, itâll be exposed here as âItem prefabâ. If you just want a quick start and donât already have a prefab for the items, or if you want to see what an example item prefab should look like, hit âGenerate example for Xâ (the prefabs for lists and grids differ from each other):
[Screenshot - see Unity Asset Store documentation for visual guide]
Itâll be highlighted in the hierarchy. The BackgroundImage and TitleText children are just for visualization:
[Screenshot - see Unity Asset Store documentation for visual guide]
After you hit Initialize, the ScrollView is configured, the ScrollRect is disabled (you can safely remove it), the scrollbar is generated and/or configured, some initial values are set for the Params and you can open the script for editing (âMyGridSRIAâ in this case):
[Screenshot - see Unity Asset Store documentation for visual guide]
You can un-comment sections marked with /**/ or remove them if they wonât be used (if you want to test the code, find the following methods and remove or just keep them commented: OnBeforeRecycleOrDisableCellViewsHolder(), GetViews(), MarkForRebuild()):
[Screenshot - see Unity Asset Store documentation for visual guide]
If you followed all the steps until now correctly, hit Play and you should see it in action:
[Screenshot - see Unity Asset Store documentation for visual guide]
- Usage
OSA, BaseParams and BaseItemViewsHolder are the 3 core classes in our small library dedicated to both optimize a Scroll View and programmatically manage its contents.
You can use them both for a horizontal or vertical ScrollView.
OSA itâs an abstract, generic MonoBehaviour, which you need to extend and provide at least the implementation of OSA.CreateViewsHolder() and OSA.UpdateViewsHolder().
Itâs recommended to manually go through the example code provided in MainExample.cs and SimpleExample.cs in order to fully understand the mechanism. Youâll find detailed comments in core areas. You may even use this script directly without implementing your own, in some very simple scenarios.
Show me the code! After youâve implemented the custom OSA class managing the views according to your specific case, adding/removing items from outside is more or less the same for any type of OSA. Lists have maximum flexibility, while Grids can only use the ResetItems method for any data manipulation. The below code uses the SimpleExample from the Demos pack to show adding of items in a List OSA:
[Screenshot - see Unity Asset Store documentation for visual guide]
When you use an OSA with multiple prefab types, adding/removing items from outside is similar:
**[Screenshot - see Unity Asset Store documentation for visual guide]**
- Implementation
Follow these steps while constantly looking at how itâs done in the example code in SimpleExample.cs and optionally in MainExample.cs
Hereâs the normal flow youâll follow if youâve created a Scroll View using GameObject->UI->Scroll View or through OSA wizard:
- Create your own implementation of BaseItemViewsHolder, letâs name it MyItemViewsHolder
- Create your own implementation of BaseParams (if needed), letâs name it MyParams
- Create your own implementation of OSA<MyParams, MyItemViewsHolder>, letâs name it MyScrollViewAdapter
- Override Start(), call base.Start(), after which:
- Call MyScrollViewAdapter.ResetItems(int count) once (and any time your dataset is changed) and the following will happen:
CollectItemsSizes() will be called (which you can optionally implement to provide your own sizes, if known beforehand). This is not the only, nor the recommended way to provide the sizes, especially on large data sets. More on that later.
CreateViewsHolder(int) will be called for each view that needs creation. Once a view is created, itâll be re-used when it goes off-viewport
newOrRecycled.root will be null, so you need to instantiate your prefab), assign it and call newOrRecycledViewsHolder.CollectViews(). Alternatively, you can call its AbstractViewsHolder.Init(..) method, which can do a lot of things for you, mainly instantiate the prefab and (if you want) call CollectViews().
after creation, only MyScrollViewAdapter.UpdateViewsHolder() will be called for it when its represented item changes and becomes visible.
(!) this method is also called when the viewportâs size grows, thus needing more items to be visible at once.
MyScrollViewAdapter.UpdateViewsHolder(MyItemViewsHolder) will be called when an item is to be displayed or simply needs updating:
use newOrRecycled.ItemIndex to get the item index, so you can retrieve its associated model from your data set
newOrRecycled.root is not null here (given the ViewsHolder was properly created in CreateViewsHolder(..)). Itâs assigned a valid object whose UI elements only need their values changed (common practice is to implement helper methods in the viewsholder that take the model and update the views themselves)
ResetItems() is also called when the viewportâs size changes (like for orientation changes on mobile or window resizing on standalone platforms)
- Grid
The documentation about implementing grids was initially created around the âGrid, horizontal layout, async items downloadâ demo from the Demos manual, because following an actual example makes it easier to both understand and see how it works.
So not only is that manual describing that particular demo, but it also contains most of the information youâll ever need about the grids, including common questions.
- Table
The TableView manual covers the basics of implementing a TableView.
- Example scenes & utilities
The demos have their own document which can be accessed here.
There youâll also find a list of all demos and their manuals.
- Playmaker support
đ©Important note for OSA 5.0 and below: The README.txt in the Playmaker support folder didnât mention an error that you get due to ASMDEFs in Unity 2017.3 and up. The updated README is now found here, where you can see the latest changes even before new OSA versions are released.
In version 4.2, Playmaker support was added. All the assets needed to implement OSA through Playmaker are in a package under /Extra/PluginSupport/Playmaker for versions older than 4.3, and /PluginSupport/Playmaker for 4.3 and up.
Check out the README file for how to import it.
Playmaker support only works for Unity 2017.1.0f3 and up + Playmaker 1.9.0 and up!
What you can do with Playmaker:
Insert/Remove/Reset/Clear items, Smooth scroll to item index
ListViews with arbitrary data and views structure (defined by you)
- GridViews
Items of variable sizes, using ContentSizeFitter. Only available for ListViews
Lazily-initializing data models (if using large amounts of items)
Displaying large amounts of arbitrary data from XML using DataMaker, although DataMaker is a bit slow for more than 10K items in our examples, so you need something faster than DataMaker if you want to display more items (we support around 2 billion)
Open our built-in DateTimePicker via a simple action and retrieve the picked date and time either in a string variable or each datetime component (year, month, minute etc.) in an integer variable in your FSM
- Use the FSMs in the existing Controllers and Item prefabs as a guide to create your own, customized ones. Itâs easy!
Check the Playmaker YouTube tutorial at the beginning of this document for how to get started and useful tips!
- Known issues, workarounds
- In several Unity versions (2018.4.9, 2018.4.34, 2019.4.10 and probably more), RectTransform doesnât correctly report its size inside Awake(). Some only behave like this if the RectTransformâs anchors are not brought together. This causes an issue when trying to initialize OSA early in Awake as opposed to doing it in Start. The issue is that the ScrollView itself reports a wrong size and is very subtle as it doesnât impact most of the users. This is because OSA automatically detects if the ScrollViewâs size changes and calls a rebuild, but in case of grids (and probably other undiscovered edge-cases) ScrollTo may not behave as expected during the same frame. Solution? Either initialize OSA in Start (general solution), or use SmoothScrollTo with a 0 duration (particular solution only for this case).
- (Not related to OSA) Some Unity 2019.1 and 2019.2 have problems with setting an Imageâs sprite property via script. See Forum thread
- (Android, but maybe other platforms have this too): On some (generally older) Unity versions, on some scenes, even if theyâre simple, Gfx.WaitForPresent() as seen in Profiler eats up between 10 and 20 FPS, and we couldnât find out why. Our fix was to build using the latest Unity at that moment, which was 2019.1. Email to lucian@thefallengames.com if you have any input on this. This is a popular issue with Unity which seems to happen randomly.
- Not actually related to the plugin itself, but worth mentioning: some lower-end devices have terrible performance with Open GL 3 and/or Auto Graphics API. If you experience oddly low FPS, untick Auto Graphics API and use Open GL 2 instead.
- In the ContentSizeFitter example scene: the prefabâs Text will be oddly truncated on some Unity versions if its Vertical Overflow property is set to Truncate. So, as a general rule, set it to Overflow when you have similar scenarios. Likewise, if you have a horizontal ScrollView, the Horizontal Overflow property is the one to be modified.
- If youâre planning to enable/disable game objects inside the CSF, then keep then disabled initially (in the prefab) if theyâll immediately be disabled anyway in the first frame after Init (and vice-versa: any GOs enabled by default in the adapterâs code, also keep them initially enabled in the prefab), because otherwise the adapter canât initialize correctly.
Concrete use case: Under the item prefab, you have a text child that has its gameObject disabled initially and it needs to activate itself on click and show arbitrarily sized data. See this forum post for more info
- Some Unity versions have a bug with their RectMask2D implementation, so if you see some demos not clipping the items inside the Viewport, you should replace it with a Mask+Image (the old way). Weâve found this to be the case for WebGL builds of Unity 2018.1 and 2018.2 when the Canvasâ space is not Overlay, but itâs a sure thing that this may also happen on other Unity versions and/or build targets
- Some users will get a call stack size exceeded âRangeErrorâ exception when building with High stripping for WebGL. You should make sure to include the OSAâs namespaces manually in your link.xml. See this post (credits to @doctorpangloss)
- Tips
- Use a Canvas as a parent of groups of frequently changing UI elements to separate them from the less frequently changing ones. Canvases can be nested. More info: Link
- Use a RectMask2D instead of Mask. It speeds Scroll Views in Unity when you donât need masking by a specific shape. Try it, but make sure to test it on different platforms, as some Unity versions have a buggy implementation of it, as stated in the Known issues section above.
FAQ
How to use the examples without the DrawerCommandPanel?
Update 24.07.2018 for v4.1: DrawerCommandPanel is now decoupled from the examples, but we still donât recommend using the example scripts in production
See Example scenes & Utilities above
- Why not use MonoBehaviour as a base class for the Items (AbstractItemViewsHolder)?
Performance.
The prefab used to instantiate the items can have any number of components on it.
You can re-use your MonoBehaviour, if thatâs where youâre already retrieving the views (or perhaps setting them via inspector). Look:
1
class MyVH : BaseItemViewsHolder{ public YourExistingMonoBehaviourType behaviour; public override void CollectViews() { base.CollectViews(); behaviour = root.GetComponent<YourExistingMonoBehaviourType>(); }}
and when youâll bind the data to the views (in UpdateViewsHolder() in our case), you can simply use your existing behavior the same way as before, just that now itâs a field in the views holder:
viewsHolder.behavior.UpdateViews (<dataModel>).
- How to do action on click/long-click?
You can borrow the code from any other scene that includes a specific action when an item is pressed.
The trick is to have a single method that handles the onClick of all items, but which is also passed the associated viewsHolder, so you can retrieve the model knowing vh.ItemIndex.
Example: When creating the views holder:
1
MyViewsHolder vh =...;vh.button.onClick.AddListener(() => OnItemClicked(vh));
Long-clicking is handled the same way, and a utility script is provided for that, in case you donât have your own. See the SelectionExample script and its associated scene for this.
Also, in case of grids, cells are created for you, so in order to execute some code, like adding a listener, right after their creation, override OnCellViewsHolderCreated.
- How to make looping work when there are fewer items than what can be shown in viewport?
Solution 1: Duplicate your models so that the number of items which the adapter sees is always bigger than the minimum needed (this number depends on several factors, like the viewportâs size and how your itemsâ size changes based on their anchors, but in practice youâll find that number to be roughly (minItemSize+spacing)/viewportSize + 1, where minItemSize is the size of the smallest item that youâll have, in case they can have different sizes).
Example of a duplicated list of 2 items, where the minimum displayed can be 4: [model1, model2, model1, model2, model1, model2]
Solution 2: Enable\disable looping on count change, based on the content-to-viewport ratio. Letâs say if content is less than 1.3x the viewportâs size, you disable looping.
You can retrieve this ratio using the extensions in IScrollRectProxy.cs.
You should choose a threshold ratio in a way that will always keep at least 1 item out of view.
- Is it possible to add/remove items from UpdateViewsHolder?
Nope. UpdateViewsHolder itâs not supposed to have the responsibility of deciding whether items should be added/removed. It just binds data to the views. Thatâs all. Iâm pretty sure you can achieve whatever youâre looking for without the need to add/remove items from UpdateViewsHolder.
- Whatâs the purpose of separate calls to _Params.Data.Add and InsertItems? Is there any useful case when they can be called without each other?
Update 24.07.2018 for v4.1: DataHelpers are now provided which do this for you, if you donât need full flexibility
In short, the biggest advantage is to be able to display items whose model wasnât yet retrieved completely (maybe youâre downloading them one by one or just downloading them when needed, i.e. when first shown).
This is needed because OSA was from the very beginning designed to be as flexible as possible. The adapter itself doesnât know about your list or how you store your data. It just asks for the items count and, optionally, their sizes (size meaning height or width, depending on ScrollViewâs orientation). How you set the sizes is exemplified throughout the demo scenes, as there are multiple ways of doing this, depending on how much variation in size you have in your data set + what you prioritize more: CPU, memory, the speed of the ChangeItemsCount call etc.
And put simply, you tell the adapter how many items there are and maybe their sizes, and thatâs all it needs to know in order to decide when to call [Create/Update]ViewsHolder for you. The way you store your data is your implementation detail, but enough utilities are already implemented for you in the Utils namespace, like the BaseParamsWithPrefabAndData class. When you call _Params.Data.Add, youâre just adding an item to the list. The adapter should also be notified of this in order to update the list of sizes, update the content size and a lot of other things, thatâs why a very rigid item adding/removing convention is needed.
Alternatively (and this is what itâs done in some of the demos), you can just create a helper method that does both of these things.
- What is frame8? There seems to be a separate set of classes contained in the frame8 namespace
Itâs our internal framework which is used in all of our projects (only a subset of it was included, to keep file count at minimum).
For v4.1 and up, its functionality was completely separated under its own folder in /Scripts/frame8 and some non-essential files were removed
- How to scroll in both directions?
The Scrollbar solution for OSA is ScrollbarFixer, but it only works in the main scrolling direction.
Weâll take a vertical List as an example.
Scrolling in both directions is not yet implemented out of the box. The easiest way is to manually increase/decrease the Contentâs localPosition.x based on your own calculations. You can have 2 arrow buttons for adding discrete increments/decrements to localPosition.x.
However, the first quick solution may not be very satisfying, so you can alternatively experiment with adding a classic horizontal ScrollRect as the parent of OSA, and make OSAâs width large enough (at edit time, so constant) to encompass enough of its childrenâs width.
Then add a usual horizontal Scrollbar, which will always be visible if you donât manually disable/enable it through code (because the OSAâs width will be constant).
Make sure OSA has ForwardDragToPrents toggled.
Make sure the parent ScrollRectâs content is OSA, and the viewport itâs none (or itâs set to ScrollRect itself).
Check Unityâs docs if youâre not very familiar with the built-in ScrollRect. The difference between the docs and our case is that you wonât have a Viewport between the top ScrollRect and OSA.
Another solution is to use something very similar to the EdgeDragger script we include in some scenes (ContentSizeFitter example, horizontal async example). This is a draggable button that allows manually dragging a UI elementâs edge, so the user will choose how large itâll be. Of course, EdgeDragger may not have production-quality code, because it was only intended to be used in the demos, but itâs very close to a final solution.
- Why canât I access Time.time or Time.deltaTime ? (Error: âfloatâ does not contain a definition for âtimeâ)
Since v5.0, you can set Parameters.UseUnscaledTime to choose whether OSA can work even if the appâs time is paused or not, so Time and DeltaTime properties were introduced and you should use those instead, for your own time-related tasks/animations.
Also, Time property has this name intentionally to prevent you from using UnityEngine.Time by accident when typing just Time . You should now use UnityEngine.Time explicitly if thatâs your intention
- CollectItemsSizes doesnât work
Youâre probably using BaseParamsWithPrefab. You need to either make your prefabâs size to be smaller than any of the items youâll ever have, or simply use BaseParams and manually add the Prefab property which youâll use in CreateViewsHolder. BaseParamsWithPrefabâs main purpose is to make the prefab control the DefaultItemSize, but if youâre overriding it anyway, BaseParamsWithPrefab is not needed, maybe just as a convenience for storing the ItemPrefab property for you.
In v5.1 (no ETA for publishing it yet) thereâs a PrefabControlsDefaultItemSize property in BaseParamsWithPrefab that youâd disable to achieve the same effect. I attached the new file here (need to be a Verified OSA User) which should be fine if you replace it in an OSA 5.0 project. And I actually recommend this instead. If the file is gone, request it via DM.
- How can I force a ListView to redraw a ViewsHolder whose modelâs data has changed? i.e. force-call UpdateViewsHolder (which normally is called only when the holder becomes visible / is created)
Problem with relying on UpdateViewsHolder for this purpose is that if youâre using ScheduleComputeVisibilityTwinPass in it (like the ContentSizeFitter example does), itâll not work. So for this reason thereâs no built-in method that you can call to have it call UpdateViewsHolder for you. But If you donât use ScheduleComputeVisibilityTwinPass, then youâll be fine (a) calling UpdateViewsHolder manually, or (b) even have an UpdateViews method in the ViewsHolder which you call directly.
If you use the ContentSizeFitter approach to have different item sizes, the solution is to use (b) and then call ForceRebuildViewsHolderAndUpdateSize on it.
In v5.1, thereâs ForceUpdateViewsHolderIfVisible, which takes care of everything, even rebuilding a single itemâs size when you use ScheduleComputeVisibilityTwinPass in UpdateViewsHolder.
How can I both scroll to- and resize an item at the same time? Like resizing the item with its center as pivot (currently, only the top and bottom edges can be stationary) AND centering it inside viewport:
**[Screenshot - see Unity Asset Store documentation for visual guide]**
This is only available to v5.1 and up (see other FAQs above for how to grab one if itâs not released yet).
You should be using ExpandCollapseAnimationState for the resizing animation, the same way itâs exemplified in MainExample.cs (from the Demos extra package). Youâll also see here what extra fields to add to your model in order to make it expandable, as well as the methods used in the code snippet at the end of this answer.
Either through inspector or in code, set Parameters.Animation.Cancel.SmoothScroll.OnSizeChanges = false. This makes sure a SmoothScroll in progress wonât be cancelled by a size change in progress, which makes them run simultaneously.
Use the following code (it uses other methods from MainExample.cs, which presumably youâve already copied in your OSA implementation when added the resizing):
1
void ToggleItemFocus(int index){ float vpSize = (float)GetViewportSize(); var model = <get model at index>; float itemNewSize = model.ExpandedAmount == 1f ? _Params.DefaultItemSize : GetModelExpandedSize(model); *// Get half of the remaining viewport space as percentage* float normalizedOffsetFromViewportStart = ((vpSize - itemNewSize) / vpSize) / 2f; SmoothScrollTo(index, <duration, same as resizing>, normalizedOffsetFromViewportStart, 0f, null, null, true); *// "Click" the item through code* var vh = GetItemViewsHolderIfVisible(index); OnExpandCollapseButtonClicked(vh);}
- How to calculate the ideal Image Pool capacity (LRUCachingPool)?
There are several approaches. It requires hard-coding some values that may need to change in a year or 2. This is usually done by each developer based on the info they have.
You can google the different ways you can get the estimated available free ram in bytes X, and then hereâs one suggestion:
1
float safePoolSizeBytes = Mathf.Min(X / 4f */*take only a quarter of the available ram*/*, 250 * 1024 * 1024 */*And limit it to 250mb because we're good citizens*/*);float avgBytesPerImage = (4f*/*PNG has 4 bytes per pixel*/* * averageWidth * averageHeight);int safePoolCapacity = (int)(safePoolSizeBytes / avgBytesPerImage);
- OSA initializes itself in Start(). Can I initialize it sooner? For example, I want to instantiate OSA from a prefab and then immediately set the data on the same frame.
You can call OSA.Init() manually before setting the data. If youâre calling it later or simply donât know if OSA did already initialize itself, check OSA.IsInitialized before.
A second solution would be to override Awake() and call Init() there. OSA will detect that and skip auto-initialization in its Start(). Note that a GameObjectâs Awake() is called only if itâs active, or at the moment of first activation.
- How to have more items [created] than the number of visible ones? I.e. more than the OSAâs calculated <visibleCount + 1>
OSA makes sure it only creates the minimum necessary objects to display your data. However, if you want to create more, one way of doing it is simply enlarging the Viewport and also increasing the padding in those directions by the same value. That way, your first item will still appear first, but it wonât immediately be recycled when goes out of the view (assuming youâre using the default setup with a Mask on the ScrollView object), because thatâs actually done when the item goes out of the viewport; and since the viewport is now larger, items need to travel a greater distance before being recycled, leading to what you want: more game objects are created than the visible ones.
Concrete example: Consider a vertical ScrollView. Viewport is initially identical to the ScrollView itself. Now move the Viewportâs Top edge 200 units up and its Bottom edge 200 units down. Increase the ContentPadding Top/Bottom by the same amount. Voila! Look in the hierarchy at runtime to see the results.
- Is there a way to pre-instantiate a set number of list items (ie. 20-50) on Start() and have the items persist even when I call ResetItems(0)?
OSA v5.0 has CreateBufferredRecycleableItems() and AddBufferredRecycleableItem(), which allow you to pre-instantiate some items and keep them in memory to avoid any subsequent object creation/destruction. For this, a special âbufferred recycleable itemsâ internal list was created, apart from the ârecycleable itemsâ, and it contains items that wonât be directly destroyed, unless theyâre âpromotedâ to the ârecycleable itemsâ list.
You can pass a custom index for the views holders: useful when having multiple prefabs and you want to distinguish between them, since CreateViewsHolder(int) is called for these. OSA calls it with -1 by default, but in case of multiple prefabs, youâll use a different negative value for each prefab type.
Something similar can also be achieved by setting a higher BaseParams.optimization.RecycleBinCapacity, which previously didnât work, but was fixed in v5.0.
As for persisting those game objects between important events like count changes, there are some parameters to control this under Params.optimization: KeepItemsPoolOnLayoutRebuild, KeepItemsPoolOnEmptyList. As of 19.07.2020, grids ignore KeepItemsPoolOnLayoutRebuild if the layout has rebuilt results in a different number of cells per group.
Ok, but how about pre-instantiating items at edit-time and using those, i.e. no instantiate allocations to make at runtime?
This is something thatâll be added in 5.1.3+ (not yet released ATM of writing - 06.11.2020). Youâll basically be able to supply the instance of your root GameObject for a particular ViewsHolder inside CreateViewsHoler(), instead of the prefab. This is done by using vh.InitWithExistingRoot():
1
\[SerializeField\]List<RectTransform> _PreMadeViews = null;*/*some code here*/*protected override void OnInitialized(){ base.OnInitialized(); *// Add our pre-made views* var vhs = CreateBufferredRecycleableItems(_PreMadeViews.Count); AddBufferredRecycleableItems(vhs); *// We don't want the buffered items to be destroyed during the OSA's lifetime* _Params.optimization.KeepItemsPoolOnEmptyList = _Params.optimization.KeepItemsPoolOnLayoutRebuild = true;}protected override MyItemViewsHolder CreateViewsHolder(int itemIndex){ var instance = new MyItemViewsHolder(); if (itemIndex < 0) { *// Called because of our CreateBufferredRecycleableItems() call* var preMadeRoot = _PreMadeViews\[_PreMadeViews.Count - 1\]; _PreMadeViews.RemoveAt(_PreMadeViews.Count - 1); instance.InitWithExistingRoot(preMadeRoot, _Params.Content, itemIndex); } else { *// Called by OSA when it needs more views to be created* *// You can decide to throw an exception or simply fallback to instantiating a new one at runtime* *//instance.Init(_Params.ItemPrefab, _Params.Content, itemIndex);* throw new InvalidOperationException("Increase the number of pre-instantiated items"); } return instance;}
- How to get the currently snapped/focused item when using Snapper8?
OSA 5.1 added OSASnapperFocusedItemInfo util script. The code is really simple and you can copy it directly into your OSA implementation, but itâs provided as a separate script for convenience.
- How to âdecorateâ the ScrollView with an additional view thatâs not necessarily related to an item? For example, show an header or advertisement-like view that scrolls with the content
A. Simple: Header that scrolls with the content
Just add your UI element (a header, in this case) as a child of the Viewport (attention, not the Content), and add an OSAContentDecorator component to it. The default parameters should work just fine for this case.
If you donât see your decorator at the top, or if you get an error, it probably means youâre manually initializing OSA via OSA.Init() and/or youâre doing it before OSAâs Start() method. Because OSAContentDecorator needs to be initialized before OSA, you have to manually call <yourDecoratorInstance>.Init(), before OSA.Init().
Hierarchy:
[Screenshot - see Unity Asset Store documentation for visual guide]
Scene:
[Screenshot - see Unity Asset Store documentation for visual guide]
Inspector (defaults):
[Screenshot - see Unity Asset Store documentation for visual guide]
Result (also showing the elasticity setting):
[Screenshot - see Unity Asset Store documentation for visual guide]
B. Advanced example: Advertisement at a specific position
The way it works is youâll have an empty item in your data set that will act as the space required for the ad. The ad will make use of the OSAContentDecorator, which is a versatile component.
Steps:
Add your ad as a child of Viewport;
Add OSAContentDecorator to it with these params:
[Screenshot - see Unity Asset Store documentation for visual guide]
Keep a reference to the decorator component in your OSA implementation - for ex., assign it to a field.
Keep your ad object deactivated from edit-time. Activate it at runtime (in code) before the first time you set the items. This way, you wonât see it just sitting there. Alternatively, you can set the Inset to a value like -1000.
Letâs say the ad should appear below item 32 (33th item) as item 33 (34th item). This means that the original item 33 in your set will become item 34, because weâll insert the empty item at 33. If you keep your data in a plain C# list, use list.Insert(adIndex, model). If you use DataHelpers, use dataHelper.Insert(adIndex, model). Itâs better to add the empty model in the C# list before submitting the change to the DataHelper, so OSA will just refresh once at init.
After OSA was initialized, we want the placeholder item to be resized so that the ad will fit into it. This can be done after youâve set the items the first time:
1
var adRT = decorator.transform as RectTransform;var adSize = adRT.rect.height; *// or width, if using a horizontal scrollview*RequestChangeItemSizeAndUpdateLayout(adIndex, adSize)
Override OnScrollPositionChanged and set keep the decoratorâs inset updated like this:
1
protected override void OnScrollPositionChanged(double normPos){ base.OnScrollPositionChanged(normPos); if (GetItemsCount() <= adIndex) *// Items not set yet or just too few items exist, which don't include the ad* return; float adInset = (float)GetItemVirtualInsetFromParentStart(adIndex); decorator.SetInset(adInset);}
If youâre using variable-sized items, youâll need to skip executing the size-changing code for the ad. Not only this, but any change done to the placeholder is unnecessary, so you wonât need to execute it at all:
1
protected override void UpdateViewsHolder(MyItemViewsHolder newOrRecycled){ if (newOrRecycled.ItemIndex == adIndex) return; *// Code for normal items* ...}
You may further want to disable the children of your item, if itâs an ad, and enable them otherwise (you cannot directly disable/enable the item because OSA manages that). This is needed if your ad doesnât cover the placeholder item completely.
If OSAContentDecorator.SetInset() doesnât exist, youâll need to update OSA or (if the live version is not yet available) contact us on discord to get a preview package as this method was added after v5.1.2.
That should be it.
- How to jump to the next/previous items when using snapping?
So snapping usually means youâll have a Snapper8 script on your OSA object. This means that thereâs always a snapped or âfocusedâ item. A good example is a gallery-like horizontal ScrollView where youâre focusing the middle item and you have other items around it. A very common need is to be able to programmatically snap to the next item and thatâs where youâll use the well-known SmoothScrollTo(), or just ScrollTo() if you want it to be immediate.
For example, youâre waiting in Update() for a key press, then get the currently focused item, and scroll to the next, if it exists:
1
protected override void Update(){ base.Update(); if (Input.GetKeyUp(KeyCode.RightArrow)) { var snapper = GetComponent<Snapper8>(); float _; var snappedVH = snapper.GetMiddleVH(out _); if (snappedVH == null) { *// Probably there are no items* return; } int nextItemIndex = snappedVH.ItemIndex \+ 1; if (nextItemIndex < GetItemsCount()) { *// No more items after the current one* return; } bool cancelCurrentAnimationIfAny = true; *// We're passing snapper.viewportSnapPivot01 and snapper.itemSnapPivot01 to preserve the snap position specified in the snapper (which may not be in the middle, if the user chooses to)* SmoothScrollTo( nextItemIndex, .3f, snapper.viewportSnapPivot01, snapper.itemSnapPivot01, null, null, cancelCurrentAnimationIfAny ); }}
| Â |
- How do I use the âNew Input Systemâ?
Unity documentation is fine, but we had trouble finding it. In any case, we tested OSA 5.3.1 with Unity 2019.4 LTS and it works fine with either input system. You just need to carefully read the Installation and the first part of UI Support sections. Once the documentation was found, it took 5 mins to set it up.
Note: for maximum compatibility, the demo scenes werenât changed to support the new system, but you can easily modify any of them - you only need to migrate the EventSystem object in them using the above instructions.
Small note: We initially tested it witwh 2019.1, but this time Unity didnât automatically create/assign the default Input Actions asset for us, so if youâre stuck with this version, check further down the UI Support page so set up your own.
- How to prevent the interaction with the items while scrolling/dragging? I want to prevent button clicks while the content is moving.
A solution would be adding an invisible Image at the same level in the hierarchy as the Content game object and only keeping it enabled while OSA.Velocity > 0. The result is the image will block any interaction with the Content object while itâs scrolling, but will still forward drag events to OSA so that dragging will work fine.
I imagine CutMovementOnPointerDown can be left ON, but if you have problems in your particular case, try turning it OFF.
How to obtain a sticky item that never goes out of screen?
**[Screenshot - see Unity Asset Store documentation for visual guide]**
Although thereâs no built-in way to do it, the simplest approach would be to use a manually-instantiated VH whose RectTransform you manage directly. Add this as a child to the Viewport or to the OSA object itself.
So as you create a vh normally:
1
protected override MyItemViewsHolder CreateViewsHolder(int itemIndex){ var instance = new MyItemViewsHolder(); instance.Init(_Params.ItemPrefab, _Params.Content, itemIndex); return instance;}
, youâll create your custom vh outside, parenting it to Viewport and giving it no index:
1
MyItemViewsHolder CreateOverlayVH() { var instance = new MyItemViewsHolder(); instance.Init(_Params.ItemPrefab, _Params.Viewport, -1); return instance; }
This vh then can be stored in a field, and then updated as any other one, since theyâre reusable by design.
The purpose would be to always display it on top of the âreal itemâ, but never moving it out of the screen.
You can have a constant check in Update() where you see if GetViewsHolderIfVisible(<the target itemIndex>) returns non-null, and if yes, update your overlay vhâs position (the vh you created via CreateOverlayVH) to the position of that real item, clamping it to the Viewportâs RectTransform.
To get the target inset (you already know the size, manuallyInstantiatedVH.root.rect.height), use realVH.root.GetInsetFromParentTopEdge(Params.Content), extension function provided in OSA to position the custom VH, which works with any RectTransform.
Then use manuallyInstantiatedVH.SetInsetAndSizeFromParentTopEdgeWithCurrentAnchors(<inset>, <height>)
A way of clamping its position would be:
1
float realItemInset = <get the inset of the real item>;float minInset = 0; *// item placed at the very top (but totally visible)*float maxInset = <viewport size> - <item size>; *// item placed at the very bottom (but totally visible)*float targetInset = Mathf.Clamp(realItemInset, minInset, maxInset);float size = manuallyInstantiatedVH.root.rect.height;manuallyInstantiatedVH.SetInsetAndSizeFromParentTopEdgeWithCurrentAnchors(targetInset, size);
- How to preserve the current view when adding/resetting items?
This is mostly wanted for Grids, since they have a limitation in this sense, but it can be done manually, as explained below. And for Lists itâs a built-in solution: simply use Insert or Remove instead of ResetItems. But you can still use ResetItems as well, if for some reason you cannot use the first 2. Hereâs the common start code (continues on next page)
1
int countBefore = GetItemsCount();TItemViewsHolder vh = GetItemViewsHolder(0); *// 0 is the index in visible items list (not the index in the data list)*int indexToScrollTo = vh.ItemIndex;var inset = GetItemRealInsetFromParentStart(vh.root);var insetNorm = inset / GetViewportSize();*// <add items at the end or at the start of the list >**// ...*
Then, for Lists:
1
*// If added at the end*ScrollTo(indexToScrollTo, (float)insetNorm, 0f);*// If added at the start*int numberOfAdditionalItems = GetItemsCount() - countBefore;indexToScrollTo \+= numberOfAdditionalItems;ScrollTo(indexToScrollTo, (float)insetNorm, 0f);
and for Grids:
1
*// If added at the endScrollToGroup(indexToScrollTo, (float)insetNorm, 0f); // for grids, vh is a CellGroupViewsHolder (i.e. a row/column of cells for vertical/horizontal ScrollViews)// If added at the start, it's a bit more work:int numberOfAdditionalCells = GetItemsCount() - countBefore;int groupIndexToScrollTo = indexToScrollTo;int cellIndexToScrollTo = groupIndexToScrollTo * GridParams.CurrentUsedNumCellsPerGroup;cellIndexToScrollTo \+= numberOfAdditionalCells;ScrollTo(cellIndexToScrollTo, (float)insetNorm, 0f); // for Grids, ScrollTo takes the cell's index*
- How to show the month name instead of a number in the Date-time picker?
Youâll have to clone DateTimePickerDialog prefab (or create a variant of if), add this script (see below) to the Month game object, assign each field in the inspector as in the old Month adapter component, and finally remove the old component.
There might be better ways to do it overall, but this pattern should really give you a lot of flexibility for any part of the datetime.
1
using System;using System.Globalization;namespace <WhaverNamespaceHere>{ public class DateTimePickerMonthNameAdapter : DateTimePickerAdapter { protected override void UpdateViewsHolder(MyItemViewsHolder newOrRecycled) { base.UpdateViewsHolder(newOrRecycled); var monthIndex = _Params.GetItemValueAtIndex(newOrRecycled.ItemIndex); newOrRecycled.titleText.text = MonthIndexToShortForm(monthIndex); } string MonthIndexToShortForm(int monthIndex) { return new DateTime(1970, monthIndex, 1).ToString("MMM", CultureInfo.InvariantCulture); } }}
Itâs really useful to not modify the OSAâs standard datetime picker prefab, as thatâll be overwritten when you update to a new version or if you re-import the embedded *.unitypackage files again.
So after you have your new prefab, itâs recommended that you just use it as-is, because (at least as of 02.12.2022), the static method DateTimePickerDialog.Show(..) doesnât allow you to specify a prefab -- this will be available in v6.1.3.
- Iâm using a custom component that handles OnPointerDown and OnPointerUp, but OSA doesnât scroll when that element is clicked on and dragged.
This is a common Unity problem, unrelated to OSA, but we have InputFieldInScrollRectFixerBase that fixes that for InputFields. You can take some inspiration from that in order to make your custom component send events to OSA. Itâs easier than you think.
- The PageView sometimes allows the user to skip 1 page if scrolling fast. How can I disable this?
This was our take at improving UX and yes, itâs subjective. For example, if the user wants to skip a page (go from 4 to 6), theyâd just swipe twice. This happens a lot when a user already knows what page 5 is (they mightâve accessed that particular screen in the app many times before and may want to skip over some pages). Itâs similar to the compounding effect on subsequent finger drags on the regular ScrollView, which builds up speed exponentially with more swipes.
One way to disable this behavior is by controlling the DragEnabled/ScrollEnabled properties:
1
protected override void Start(){ ... _Params.Snapper.SnappingStarted \+= Snapper_SnappingStarted; _Params.Snapper.SnappingEndedOrCancelled \+= Snapper_SnappingEndedOrCancelled;}void Snapper_SnappingStarted(){ _Params.DragEnabled = _Params.ScrollEnabled = false;}void Snapper_SnappingEndedOrCancelled(){ _Params.DragEnabled = _Params.ScrollEnabled = true;}
This way, the user wonât be able to swipe while thereâs a snapping animation in progress. For this to work, you need OSA v6.2.3 (a relevant bug was fixed here).
- How to place the first item at the bottom (reverse list)?
This is a common request. For example, a chat (usually) needs new messages to be inserted at the bottom. OSAâs complexity couldnât have allowed an out of the box solution for this. It simply relies on your basic understanding of the plugin to configure your own tweaks, which go far beyond just reversing the list.
Now, to the point: Simply reverse your underlying list in the code. We feel this is so easy and convenient that adding another layer of complexity in OSA just for this is not a convenient trade-off.
Additionally, you might want to insert an item at the bottom and push the existing ones upwards: in this case, just use true for the keepEndEdgeStationary parameter when adding a new item, and also for ScheduleComputeVisibilityTwinPass if you use the CSF pattern (if you donât know what this is, ignore it).
- How to improve responsiveness, i.e. preserve itemsâ positions (and ideally the velocity) between resizing OSA itself?
Enable this in OSAâs inspector (or in code): Params -> Optimization -> ResponsiveOnScrollViewSizeChange
Disabled by default because in most cases this already works, but in cases such as using CSF and dynamic size changes of items, we need some additional magic.
- Help improve OSA by leaving a review
Your review will lead to more users purchasing the asset, and this indirectly provides the necessary resources to improve it and keep up with your feature-requests!
And youâll have my everlasting gratitude. đ
-- Lucian
