Post
The blog is a work-in-progress started on Oct 8 '25, expected to last a few weeks

OSA Demo: Item Dragging and Reordering

Learn how to implement drag-and-drop functionality in your OSA scroll views. This demo shows you how to enable item reordering through intuitive drag interactions.

Contents

  1. Description
  2. Scene
  3. Code
  4. Summary

  5. Description

This doc refers to the item_dragging scene, and it’s not supposed to be an introductory read for OSA. You should already be familiar with the basics from the main manual and the Demos manual.

As you can see in the scene when you run it, you can reorder items inside a list, but also drag them to another list (as long as you make sure the IDs are global) or outside.

This is mainly supported by the OSA.RemoveItemWithViewsHolder(), OSA.InsertItemWithViewsHolder(), as well as DraggableItem script found under /Scripts/Util/ItemDragging (which is an independent script that you can also use in other contexts). The rest consists of the usual OSA implementation and some linking code, notably the drag/drop event handling.

The script DraggableItem catches the drag/drop UI events, as well as the long-press event and asks its current owner what it should do with them. The concept of an owner here refers to the deck that currently contains the item, and implements DraggableItem.IDragDropListener.

If the item is orphaned, i.e. outside a deck, you can freely drag it. At the moment you hover it over an object that implements IDragDropListener (i.e. a new potential owner) it detects that and it updates its appearance to let you know you can drag it there. After you submit the drop, that IDragDropListener is asked whether it accepts the drop, and if yes, it becomes the new owner of the item; if not, the item is dropped there as if it was empty space, and will remain orphaned.

Reordering items within a list works similarly, with some extra nice effects because of easier owner-item communication.

  1. Scene

[Screenshot - see Unity Asset Store documentation for visual guide]

You can find scene-related notes below the visible canvas. As of 31.03.2020, there are 3 notes: the first and most important one states that in order to scale the dragged item (for visual feedback), we need to do it through a child of it, so we aren’t technically scaling the object, but an intermediary child of it which holds the actual views. Naming it ScalableViews seems like a good convention. The reason items can’t be scaled externally resides in the fact that OSA needs to have full control over the items’ root transform.

Same general structure as for any list ScrollView:[Screenshot - see Unity Asset Store documentation for visual guide]

What’s different from a simple list here is we have 2 “decks” (just an arbitrary term). You can have as many as needed or just one. We have 2 just to be able to demonstrate moving items between them.

You can see the rest of the structure is standard: Viewport, Content, Scrollbar etc.

The prefab:

[Screenshot - see Unity Asset Store documentation for visual guide]The prefab has the script DraggableItem on it. And, as previously mentioned, all of its actual views lie under the ScalableViews child. Both decks share this prefab, of course.

Also, in order for it to be draggable, it needs a graphic component with Raycast Target checked.

  1. Code

The ItemDraggingSceneEntry 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 notify the adapter about it. Before doing so, we’re generating all of the models. The fact that we reference deck B inside this script may be confusing, but we’re only doing so to distinguish between the 2 decks and generate models with negative IDs so that they’ll all be globally different. In a real-case scenario, your models will already have unique ids, so there’ll be no need to think about these things.

Ignore other code in that file, as they’re simply part of the demo scene, button events and such (hopefully, you’ve read the Demos manual and already know this).

Now open the DraggableItem script for a quick look.

This is a self-containing utility and it’s not very important to know how it works in detail. The general flow explained in the beginning is enough. But you still need to know how to use it.

It has the IDragDropListener inner interface which your OSAs will implement.

It has the dragDropListener field which is expected to be assigned externally when it’ll have a parent (more on that later).

It exposes some useful properties, including State, so owners can properly manage it.

And last but not least, the inner class OrphanedItemBundle contains useful information for items that’ll be detached from their owner. At the moment an item is dropped outside a deck (IDragDropListener.OnDroppedItem() returns a non-null result), an instance of this class should be created (having the fields set respectively) and will be accessible via DraggableItem.OrphanedBundle property. With all that info, any new owner will be able to correctly integrate the orphaned item into its list.

Now open the ItemDraggingExample 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().

_DragManager is a small component that keeps the dragging state consistent and mostly separated from the adapter itself.

_Canvas and _CanvasRT are just cached for better performance. They’re needed to handle the UI events from the deck’s perspective.

We’ll come back to ItemDraggingExample after we’ll go over the smaller classes it uses.

Inner classes

MyParams is similar to the simpler demos, but it contains some useful properties that customize the dragging experience. Their names should already tell you what they do. For ex, minDistFromEdgeToBeginScroll01 is normalized (0-1) relative to the dragSpace’s size. Of course, you may not need all of those properties. You can choose to alter your items’ appearance when they’re dragged in a different way, not only coloring and scaling.

MyModel is straightforward. It needs an id so that items can be distinguished between each other (normally, in a non-reorderable list, the item’s position is enough to identify it). In a real-case scenario you’ll probably need to rename this to MyBaseModel from which to derive your arbitrarily-complex model, and then you can also rename MyViewsHolder to MyBaseViewsHolder from which you derive your arbitrarily-complex ViewsHolder, so that you can reuse your new OSA implementation for any kind of model/ViewsHolder.

EmptyModel is needed for the visual effect of placing an empty space inside the list when you’re holding an item and moving it around. The way it works is we’ll insert an actual ViewsHolder there, but since it’ll detect that it represents an EmptyModel, it’ll de-activate its views, so you’re left with an empty space, like you see in most apps where you can reorder items in a list. Its placeholderForIndex property represents which item it currently replaces. Only 1 instance of this model will be created, since you only have 1 space when dragging. This one inherits directly from MyModel.

MyViewsHolder defines the views that will present your model’s data and where they are relative to the prefab’s root.

DragStateManager, as previously mentioned, keeps track of the current state of dragging, but from the deck’s perspective (and the ItemDragger script manages the dragging from the item’s perspective). It’s nice to have this data separated and clearly defined, instead of having several fields inside the adapter itself.

Now let’s head back to ItemDraggingExample where all of these are nicely stitched. We’ll not go line by line because it’s just too much detail, and almost all of the code here will be copied as is to your own implementation, anyway (which you’ll modify as needed, after the fact).

OSA overrides

ItemDraggingExample.Start():

Just caching stuff.

ItemDraggingExample.Update():

Mostly code for auto-scrolling when you reorder the list.

ItemDraggingExample.CollectItemsSizes():

This assigns random sizes to the items, for demo purposes. You don’t need to override it, unless you want to set different sizes to your items (but better check the OSA’s main manual for that, as there are multiple ways of doing that, with pros/cons explained).

ItemDraggingExample.CreateViewsHolder():

The ViewsHolder is instantiated and its dragDropListener property is assigned this, meaning the deck is the initial owner of the item. Makes sense, right?

ItemDraggingExample.UpdateViewsHolder():

Just binding data to the views, but as you can see, and explained somewhere above, an item can represent either a MyModel or an EmptyModel, so we take care of that too.

Now the implementation of DraggableItem.IDragDropListener

This is less important, but keep reading if you’re interested.

ItemDraggingExample.OnPrepareToDragItem():

An item is preparing to be detached. We return false if we don’t recognize it. Otherwise, we detach it, and start dragging.

ItemDraggingExample.OnBeginDragItem():

This is called after the previous call was confirmed, and corresponds to Unity’s IBeginDragHandler.OnBeginDrag. We notify _DragManager about it.

ItemDraggingExample.OnDraggedItem():

This is called each frame in which you drag the item ever so slightly, and corresponds to Unity’s IDragHandler.OnDrag(). We show some visuals and move the empty ViewsHolder accordingly to the new space so that the user will see where the dragged item will end up if they drop it.

ItemDraggingExample.OnDroppedItem():

Called when you release the item inside. It checks whether the item was dropped inside (returns null), or outside (returns an instance of OrphanedItemBundle which future owners use to integrate the item into their own list). In each case, it then cleans the deck’s dragging state by removing the empty model etc.

As of v5.0, all of the necessary checks are done in the method DropDraggedVHAndEnterNoneState(). No need go into its details.

ItemDraggingExample.OnDroppedExternalItem():

This is called when an outside item is dropped on this deck. It can be an item that comes directly from another deck or an item that was first orphaned and now dragged-and-dropped here. The method decides what to do in each case, and if it accepts the drop, this deck becomes the new owner of that item.

Implementation of ICancelHandler

ItemDraggingExample.OnCancel():

Not needed in most cases, but provided to demonstrate how you could cancel an on-going drag when, for ex., something forces you to close the current menus, but not the entire scene, a case in which we don’t want to be left hanging in an undefined state.

The rest of the methods you see are just utilities and it’s not important to understand them.

  1. Summary

  2. You can have one or more lists, here also called decks, that you can drag your items between, and the lists share the same prefab.

  3. Items can be dragged outside, then to any of the available decks.

  4. Each item has a current owner. A null owner means it’s orphaned, i.e. placed outside.

  5. Items need to be identifiable via IDs. If you have multiple decks and you want to drag items between them, they should all have unique IDs to prevent having 2 items with the same ID in the same deck.

  6. Most of the code should be copied as it is and modified afterwards, as with other demos. You’ll only need to modify (and maybe rename) the Params, ViewsHolder and the Model classes

  7. If you want to reuse your new OSA implementation for any kind of model/ViewsHolder, rename MyModel to MyBaseModel from which to derive your arbitrarily-complex model, and then rename MyViewsHolder to MyBaseViewsHolder from which you derive your arbitrarily-complex ViewsHolder. For it to work, you also need to add 2 generic arguments to the new implementation’s class, one for the model and one for the ViewsHolder, and add these base types as generic constraints, and also pass the same type arguments to OSA, after which you’ll be prompted by the compiler to correct some errors. Then you’ll just create a non-generic class with no members which derives from your new implementation and specifies which combination of model and ViewsHolder should be used. That can be attached to a scroll view (potentially created via OSA Wizard)

this post is licensed under cc by 4.0 by the author.

© Forbidden Byte. some rights reserved.

other searches include optimised scrollview adapter, optimized scroll view adapter, optimized scrollvew adapter, optimzed scrollview adapter, optmized scrollview adapter, optimized scrolview adapter, optimized scrollview adaptor, optimized scrollveiw adapter, optimized scrollview adpater, osa unity, mediaplayer8, media player 8, mediaplayer8 unity, media player unity, scene objects query, sceneobjectsquery, scene object query, soq unity, unity video player plugin, unity scene query tool, scroll rect optimizer, unity list view, unity grid view