Hey, I had some time and needed to work on a Reorderable List system for one of my project. Here is a preview: It's waiting a pull request from Unity-UI-Extensions Make sure to get it when it has been approuved Cheers
Fantastic work @Ziboo , saw your PR, will get this reviewed and committed as soon as I can. Would be great to see this also working horizontally and in a grid as well. (maybe even in the radial layout??)
Great work on the update, re-consuming that now. However I did note that you have a ContentSizeFitter on the child content, which according to the Unity UI Rules is invalid (gives you a warning as well). Although , I also note that be removing that, it causes some issues when dropping content back in to the list. Any ideas?
I wanted to layout my test lists quickly so I made some dirty things in the scene (Lists>Column x) But, the list itself has a pretty standard setup: - ScrollRect -- Content (LayoutGroup + ContentSizeFitter) ---Element (LayoutElement) ---Element (LayoutElement) ---Element (LayoutElement)
Just tweeted, but copy here as well, if I try this setup, dropping / reordering of elements doesn't work: - EmptyGO (with Reorderable Script) -- Content (LayoutGroup + ContentSizeFitter) ---Element (LayoutElement) ---Element (LayoutElement) ---Element (LayoutElement)
Yep you're right ! The object having the Reorderable Script needs to have a UI element. It's needed when doing a raycast to see which list is under the cursor
Ahh right, ok continuing testing. I've done an initial checkin after consuming your code, so be sure to update your fork before any further updates. *edit = Yup, that works!
OK, hopefully last question. What is the purpose of the "Dragable Area"? It seems to just be the top level point to hold the "fakes" while dragging. Why not just use the GO with the script? Right, I see you use that to keep the Dragged component "on top" of the other elements. making a slight update to make this optional and use the top level canvas as a default.
Because you can have a mask on the GO with the script. It's typicaly how I do my setup - ScrollRect (Image + Mask) -- Content (LayoutGroup + ContentSizeFitter) ---Element (LayoutElement) ---... Small improvement maybe would be the select the top parent canvas if Dragable Area is not set...
Yes, that is what I've done. I've also added additional events for grabbing an item and removing an item from a list . Should have the update pushed up soon, then I can back port it to 4.6 and push it to the 5.3 release ;S
#UIExtensions updated to V1.05, complete with the Re-Orderable Lists, editor extensions and a few of my own tweaks Thanks for the support!, Keep it coming.
Hello, I tried to use reorderable list from UIExtensions, but stuck with a weird bug. I have several Text elements within a list element. After I drag&drop this element inside the same list, most of the text elements go blank. In the inspector: their sizes appear correct they have text they are enabled and visible hierarchy is correct But despite all that I can't see any text in 3 out of 4 text elements. UPD: While I was writing, I figured out what's wrong. After drag&drop Text elements became slightly smaller in height which caused text to disappear. I added Layout Element to them with Min Height set and it solved the problem.
Another bug was raised on the ReOrderable list. If it is on a Screen Space - Camera (with camera set) or World Space Canvas, it produces some weird results. Drag and drop doesn't work, elements get merged, etc. I suspect it is simply to do with the different coordinate system used in these cases. Any chance you can have a look?
Hi, I'm sorry but I really don't have the time... I shared the script as is, and it's very cool that it joined the collection, but I'm not sure I will work on it again, except if I need it in one of my project, then I will improve and share it again. Sorry guys, hope someone will have the time to check on it. Good luck Cheers
Ok, no worries @Ziboo I'm in the same boat really. Only asked since you were the author if you had any thoughts on the picking / dragging coordinates and could offer support. I'll look again if I have the time while packaging this release, else the community can pitch in. Failing that, there's always adding a check to only allow it to be used in SS-Overlay for now
My guess is that's maybe when I calculate where the dragged element should go in the script: https://bitbucket.org/ddreaper/unit...s?at=develop_5.3&fileviewer=file-view-default Line: 132-148 But not sure, I didn't test it with a canvas other than Overlay
Hi @SimonDarksideJ and @Ziboo I find UI extensions project interesting yet too complicated for me... learning... however took a look at Reorderable List code. DL'd and opened latest 5.3 package and opened "ReorderableList" test scene. And if I didn't mess anything right at start, it doesn't work with Screen Space Camera and World space camera at all. Dragged item will be offset from mouse cursor if editor game viewport is scaled. Seems like problem could be mostly on line 103, instead of 132-148. Code (csharp): //Set dragging object on cursor _draggingObject.position = eventData.position; Eventsystem position is used for dragged object position. IMHO It's not going to work based on screen position (many problems). Instead so far on my own projects, I've resorted to RectTransformUtility, and I've worked coordinates in respective parents space (or sometimes in worldspace). As dragged object seems to be always child of "Main" RT, and it seems to be available as _reorderableList.DraggableArea, it could be used as parent in this case: Code (csharp): // Screen to rect space conversion Vector2 lPos = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle(_reorderableList.DraggableArea, eventData.position, eventData.enterEventCamera, out lPos); _draggingObject.localPosition = lPos; With this, screen viewport scaling does not matter with Screen Space camera, World space camera works, it can be rotated, scaled, and Main RT too can be scaled, and Main can be of different size than whole canvas, dragged item should stay on cursor despite of these operations. There seems to be another issues with dragged item locking into center of canvas a some moments, when dragging near other rectTransforms, didn't try to figure it out...maybe it's caused by this change. Well, anyway, shoot this down if it's something else...
@SimonDarksideJ - Another strange choice seems to be the fact that in demo scene, ("ReorderableList" file) main canvas has Canvas Scaler, and it's set to "Constant Pixel Size" which doesn't result in anything meaningful... I've mostly only used "Scale with Screen Size" -> results are more predictable. So maybe this too has caused some false alarms?
Nah because in the UI Extensions I extract all that so you can create it fro the menu, so it doesn't seem to make any difference there. From past experience it is the Coordinate system used between SS-O and SS-C/WS, one uses screenspace, the other world coord's
@SimonDarksideJ I'm not sure if you understood what I meant with my second post, I tried to say, if demo scene UI scales along Unity editor screen size, like it does now in provided demo scene, it can look really messed up, on smaller viewport sizes, you can barely see buttons inside scroll rects, so some people might think it's somehow broken... That dragged sprite offset is definitely offset based on editor viewport size.
Should I be able to move things from one list to another in code? Right now, I'm trying to add a "reset' ability to put a list back to its original state after a user has dragged a bunch of things out of it, but once I reparent my elements back to their original list, the drag-n-drop ability gets munched a bit. When I drag something out of my list after a reset, the position snapping doesn't work until I drop the element in the new list, and then regrab it. So it seems like the lists don't realize I've moved things out of them if I do it through code. Is there a better way to send items from list to list, and make sure the lists realize what's going on? Thanks, -Rich
It's all in the options. If you look at the demo scene, you can control how child elements are dragged from (cloned / move) and dropped (add / replace / etc) as well as whether a list supports dropping. If you want to reset a list, then your going to need either a prefab of it's original state, destroy the old and read the prefab. Or manage the children and rebuild the children for you. There is no state management as part of the control. Hope that helps.
The simplest way I can think of, is to ensure each item is unique and has a unique name. Then simply walk through the Child list and serialise the list of names in that order. Then when restoring, just instantiate each child in that same order. When children are reordered, it's their order in the child transform hierarchy that is finally ordered.
Anyone tried this with variable size elements in the target list and know of a solution to the following problem? There seems to be jitter on the element to be move up or down when moving a dragged element over it and that list element is not the same size as the 1st, and dragged, element. I tested with the 2nd top row list having its red element's min height set to 50 rather than 30.
Hey, awesome component! Would this potentially support a smooth movement of the items around the "Fake" cell (like in iOS)?
When you drag an item out and back in, it always resizes to 100x100, even if preferred width and height are changed. Can't get it to stay 64x64
This commit has a fix to it: https://bitbucket.org/UnityUIExtens...s/Controls/ReorderableList/ReorderableList.cs
For people finding this thread looking for a fix to the issues with Screen Space - Camera canvases, the following worked for me: 1) Be sure there is a Camera attached to the Canvas you are using 2) Replace the for loop in the OneDrag method of ReorderableListElement.cs with the following: Code (CSharp): var screenPoint = new Vector3(); if (canvas.renderMode == RenderMode.ScreenSpaceCamera) { screenPoint = canvas.worldCamera.WorldToScreenPoint(_draggingObject.position); } for (var j = 0; j < _currentReorderableListRaycasted.Content.childCount; j++) { var childJ = _currentReorderableListRaycasted.Content.GetChild(j).GetComponent<RectTransform>(); var childJScreenPoint = _camera.WorldToScreenPoint(childJ.position); // Debug.Log("Screen positions of child | event" + childJScreenPoint + " | " + screenPoint); if (canvas.renderMode == RenderMode.ScreenSpaceCamera) { switch (_currentReorderableListRaycasted.ContentLayout) { case VerticalLayoutGroup _: dist = Mathf.Abs(childJScreenPoint.y - screenPoint.y); break; case HorizontalLayoutGroup _: dist = Mathf.Abs(childJScreenPoint.x - screenPoint.x); break; case GridLayoutGroup _: dist = (Mathf.Abs(childJScreenPoint.x - screenPoint.x) + Mathf.Abs(childJScreenPoint.y - screenPoint.y)); break; } } else { if (_currentReorderableListRaycasted.ContentLayout is VerticalLayoutGroup) dist = Mathf.Abs(childJ.position.y - worldPoint.y); else if (_currentReorderableListRaycasted.ContentLayout is HorizontalLayoutGroup) dist = Mathf.Abs(childJ.position.x - worldPoint.x); else if (_currentReorderableListRaycasted.ContentLayout is GridLayoutGroup) dist = (Mathf.Abs(childJ.position.x - worldPoint.x) + Mathf.Abs(childJ.position.y - worldPoint.y)); } if (!(dist < minDistance)) continue; minDistance = dist; targetIndex = j; }
Hi, I'm trying to figure out how to drag the items on the spot I clicked on, so I created an offset variable and added the following code to the ReorderableListElement (pseudo): Code (CSharp): Vector3 _dragOffset; void OnBeginDrag() { ..dev code.. _dragOffset = draggingObject.position - Input.mousePosition; } void OnDrag() { ..dev code.. Vector3 worldPoint; RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas.GetComponent<RectTransform>(), eventData.position, canvas.renderMode != RenderMode.ScreenSpaceOverlay ? canvas.worldCamera : null, out worldPoint); _draggingObject.position = worldPoint + _dragOffset; ..dev code.. } Unfortunately this only works partly and something is still off. I'm not a pro coder, so I'd really appreciate some help.
Okay, got it working. Just in case anyone needs this functionality (make the element stick to the cursor where it was clicked): Code (CSharp): /// Credit Ziboo, Andrew Quesenberry /// Sourced from - http://forum.unity3d.com/threads/free-reorderable-list.364600/ /// Last Child Fix - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/70/all-re-orderable-lists-cause-a-transform using System; using System.Collections.Generic; using UnityEngine.EventSystems; namespace UnityEngine.UI.Extensions { [RequireComponent(typeof(RectTransform), typeof(LayoutElement))] public class ReorderableListElement : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler { [Tooltip("Can this element be dragged?")] [SerializeField] private bool IsGrabbable = true; [Tooltip("Can this element be transfered to another list")] [SerializeField] private bool _isTransferable = true; [Tooltip("Can this element be dropped in space?")] [SerializeField] private bool isDroppableInSpace = false; Vector3 _dragOffset; Vector3 _startPos; public bool IsTransferable { get { return _isTransferable; } set { _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>(); _canvasGroup.blocksRaycasts = value; _isTransferable = value; } } private readonly List<RaycastResult> _raycastResults = new List<RaycastResult>(); private ReorderableList _currentReorderableListRaycasted; private int _fromIndex; private RectTransform _draggingObject; private LayoutElement _draggingObjectLE; private Vector2 _draggingObjectOriginalSize; private RectTransform _fakeElement; private LayoutElement _fakeElementLE; private int _displacedFromIndex; private RectTransform _displacedObject; private LayoutElement _displacedObjectLE; private Vector2 _displacedObjectOriginalSize; private ReorderableList _displacedObjectOriginList; private bool _isDragging; private RectTransform _rect; private ReorderableList _reorderableList; private CanvasGroup _canvasGroup; internal bool isValid; #region IBeginDragHandler Members public void OnBeginDrag(PointerEventData eventData) { if (!_canvasGroup) { _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>(); } _canvasGroup.blocksRaycasts = false; isValid = true; if (_reorderableList == null) return; //Can't drag, return... if (!_reorderableList.IsDraggable || !this.IsGrabbable) { _draggingObject = null; return; } //If not CloneDraggedObject just set draggingObject to this gameobject if (_reorderableList.CloneDraggedObject == false) { _draggingObject = _rect; _fromIndex = _rect.GetSiblingIndex(); _displacedFromIndex = -1; //Send OnElementRemoved Event if (_reorderableList.OnElementRemoved != null) { _reorderableList.OnElementRemoved.Invoke(new ReorderableList.ReorderableListEventStruct { DroppedObject = _draggingObject.gameObject, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject, FromList = _reorderableList, FromIndex = _fromIndex, }); } if (isValid == false) { _draggingObject = null; return; } } else { //Else Duplicate GameObject clone = (GameObject)Instantiate(gameObject); _draggingObject = clone.GetComponent<RectTransform>(); } //Put _dragging object into the dragging area //_dragOffset = _draggingObject.position - Input.mousePosition; //_dragOffset.y += 5; _draggingObjectOriginalSize = gameObject.GetComponent<RectTransform>().rect.size; _draggingObjectLE = _draggingObject.GetComponent<LayoutElement>(); _draggingObject.SetParent(_reorderableList.DraggableArea, true); _draggingObject.SetAsLastSibling(); _reorderableList.Refresh(); //Create a fake element for previewing placement _fakeElement = new GameObject("Fake").AddComponent<RectTransform>(); _fakeElementLE = _fakeElement.gameObject.AddComponent<LayoutElement>(); RefreshSizes(); //Send OnElementGrabbed Event if (_reorderableList.OnElementGrabbed != null) { _reorderableList.OnElementGrabbed.Invoke(new ReorderableList.ReorderableListEventStruct { DroppedObject = _draggingObject.gameObject, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject, FromList = _reorderableList, FromIndex = _fromIndex, }); if (!isValid) { CancelDrag(); return; } } _isDragging = true; _dragOffset = /*new Vector3(_draggingObject.position.x, _draggingObject.position.y, 0) -*/ Input.mousePosition; _startPos = _draggingObject.position; } #endregion #region IDragHandler Members public void OnDrag(PointerEventData eventData) { if (!_isDragging) return; if (!isValid) { CancelDrag(); return; } //Set dragging object on cursor var canvas = _draggingObject.GetComponentInParent<Canvas>(); Vector3 worldPoint; RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas.GetComponent<RectTransform>(), eventData.position, canvas.renderMode != RenderMode.ScreenSpaceOverlay ? canvas.worldCamera : null, out worldPoint); Vector3 diff = Input.mousePosition - _dragOffset; Vector3 pos = _startPos + diff; _draggingObject.position = pos; // worldPoint + Input.mousePosition; ReorderableList _oldReorderableListRaycasted = _currentReorderableListRaycasted; //Check everything under the cursor to find a ReorderableList EventSystem.current.RaycastAll(eventData, _raycastResults); for (int i = 0; i < _raycastResults.Count; i++) { _currentReorderableListRaycasted = _raycastResults[i].gameObject.GetComponent<ReorderableList>(); if (_currentReorderableListRaycasted != null) { break; } } //If nothing found or the list is not dropable, put the fake element outside if (_currentReorderableListRaycasted == null || _currentReorderableListRaycasted.IsDropable == false // || (_oldReorderableListRaycasted != _reorderableList && !IsTransferable) || ((_fakeElement.parent == _currentReorderableListRaycasted.Content ? _currentReorderableListRaycasted.Content.childCount - 1 : _currentReorderableListRaycasted.Content.childCount) >= _currentReorderableListRaycasted.maxItems && !_currentReorderableListRaycasted.IsDisplacable) || _currentReorderableListRaycasted.maxItems <= 0) { RefreshSizes(); _fakeElement.transform.SetParent(_reorderableList.DraggableArea, false); // revert the displaced element when not hovering over its list if (_displacedObject != null) { revertDisplacedElement(); } } //Else find the best position on the list and put fake element on the right index else { if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content) { _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false); } float minDistance = float.PositiveInfinity; int targetIndex = 0; float dist = 0; for (int j = 0; j < _currentReorderableListRaycasted.Content.childCount; j++) { var c = _currentReorderableListRaycasted.Content.GetChild(j).GetComponent<RectTransform>(); if (_currentReorderableListRaycasted.ContentLayout is VerticalLayoutGroup) dist = Mathf.Abs(c.position.y - pos.y); else if (_currentReorderableListRaycasted.ContentLayout is HorizontalLayoutGroup) dist = Mathf.Abs(c.position.x - pos.x); else if (_currentReorderableListRaycasted.ContentLayout is GridLayoutGroup) dist = (Mathf.Abs(c.position.x - pos.x) + Mathf.Abs(c.position.y - pos.y)); if (dist < minDistance) { minDistance = dist; targetIndex = j; } } if ((_currentReorderableListRaycasted != _oldReorderableListRaycasted || targetIndex != _displacedFromIndex) && _currentReorderableListRaycasted.Content.childCount == _currentReorderableListRaycasted.maxItems) { Transform toDisplace = _currentReorderableListRaycasted.Content.GetChild(targetIndex); if (_displacedObject != null) { revertDisplacedElement(); if (_currentReorderableListRaycasted.Content.childCount > _currentReorderableListRaycasted.maxItems) { displaceElement(targetIndex, toDisplace); } } else if (_fakeElement.parent != _currentReorderableListRaycasted.Content) { _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false); displaceElement(targetIndex, toDisplace); } } RefreshSizes(); _fakeElement.SetSiblingIndex(targetIndex); _fakeElement.gameObject.SetActive(true); } } #endregion #region Displacement private void displaceElement(int targetIndex, Transform displaced) { _displacedFromIndex = targetIndex; _displacedObjectOriginList = _currentReorderableListRaycasted; _displacedObject = displaced.GetComponent<RectTransform>(); _displacedObjectLE = _displacedObject.GetComponent<LayoutElement>(); _displacedObjectOriginalSize = _displacedObject.rect.size; var args = new ReorderableList.ReorderableListEventStruct { DroppedObject = _displacedObject.gameObject, FromList = _currentReorderableListRaycasted, FromIndex = targetIndex, }; int c = _fakeElement.parent == _reorderableList.Content ? _reorderableList.Content.childCount - 1 : _reorderableList.Content.childCount; if (_reorderableList.IsDropable && c < _reorderableList.maxItems && _displacedObject.GetComponent<ReorderableListElement>().IsTransferable) { _displacedObjectLE.preferredWidth = _draggingObjectOriginalSize.x; _displacedObjectLE.preferredHeight = _draggingObjectOriginalSize.y; _displacedObject.SetParent(_reorderableList.Content, false); _displacedObject.rotation = _reorderableList.transform.rotation; _displacedObject.SetSiblingIndex(_fromIndex); // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct _reorderableList.Refresh(); _currentReorderableListRaycasted.Refresh(); args.ToList = _reorderableList; args.ToIndex = _fromIndex; _reorderableList.OnElementDisplacedTo.Invoke(args); _reorderableList.OnElementAdded.Invoke(args); } else if (_displacedObject.GetComponent<ReorderableListElement>().isDroppableInSpace) { _displacedObject.SetParent(_currentReorderableListRaycasted.DraggableArea, true); _currentReorderableListRaycasted.Refresh(); _displacedObject.position += new Vector3(_draggingObjectOriginalSize.x / 2, _draggingObjectOriginalSize.y / 2, 0); } else { _displacedObject.SetParent(null, true); _displacedObjectOriginList.Refresh(); _displacedObject.gameObject.SetActive(false); } _displacedObjectOriginList.OnElementDisplacedFrom.Invoke(args); _reorderableList.OnElementRemoved.Invoke(args); } private void revertDisplacedElement() { var args = new ReorderableList.ReorderableListEventStruct { DroppedObject = _displacedObject.gameObject, FromList = _displacedObjectOriginList, FromIndex = _displacedFromIndex, }; if (_displacedObject.parent != null) { args.ToList = _reorderableList; args.ToIndex = _fromIndex; } _displacedObjectLE.preferredWidth = _displacedObjectOriginalSize.x; _displacedObjectLE.preferredHeight = _displacedObjectOriginalSize.y; _displacedObject.SetParent(_displacedObjectOriginList.Content, false); _displacedObject.rotation = _displacedObjectOriginList.transform.rotation; _displacedObject.SetSiblingIndex(_displacedFromIndex); _displacedObject.gameObject.SetActive(true); // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct _reorderableList.Refresh(); _displacedObjectOriginList.Refresh(); if (args.ToList != null) { _reorderableList.OnElementDisplacedToReturned.Invoke(args); _reorderableList.OnElementRemoved.Invoke(args); } _displacedObjectOriginList.OnElementDisplacedFromReturned.Invoke(args); _displacedObjectOriginList.OnElementAdded.Invoke(args); _displacedFromIndex = -1; _displacedObjectOriginList = null; _displacedObject = null; _displacedObjectLE = null; } public void finishDisplacingElement() { if (_displacedObject.parent == null) { Destroy(_displacedObject.gameObject); } _displacedFromIndex = -1; _displacedObjectOriginList = null; _displacedObject = null; _displacedObjectLE = null; } #endregion #region IEndDragHandler Members public void OnEndDrag(PointerEventData eventData) { _isDragging = false; if (_draggingObject != null) { //If we have a ReorderableList that is dropable //Put the dragged object into the content and at the right index if (_currentReorderableListRaycasted != null && _fakeElement.parent == _currentReorderableListRaycasted.Content) { var args = new ReorderableList.ReorderableListEventStruct { DroppedObject = _draggingObject.gameObject, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject, FromList = _reorderableList, FromIndex = _fromIndex, ToList = _currentReorderableListRaycasted, ToIndex = _fakeElement.GetSiblingIndex() }; //Send OnelementDropped Event if (_reorderableList && _reorderableList.OnElementDropped != null) { _reorderableList.OnElementDropped.Invoke(args); } if (!isValid) { CancelDrag(); return; } RefreshSizes(); _draggingObject.SetParent(_currentReorderableListRaycasted.Content, false); _draggingObject.rotation = _currentReorderableListRaycasted.transform.rotation; _draggingObject.SetSiblingIndex(_fakeElement.GetSiblingIndex()); //If the item is transferable, it can be dragged out again if (IsTransferable) { var cg = _draggingObject.GetComponent<CanvasGroup>(); cg.blocksRaycasts = true; } // Force refreshing both lists because otherwise we get inappropriate FromList in ReorderableListEventStruct _reorderableList.Refresh(); _currentReorderableListRaycasted.Refresh(); _reorderableList.OnElementAdded.Invoke(args); if (_displacedObject != null) { finishDisplacingElement(); } if (!isValid) throw new Exception("It's too late to cancel the Transfer! Do so in OnElementDropped!"); } else { //We don't have an ReorderableList if (this.isDroppableInSpace) { _reorderableList.OnElementDropped.Invoke(new ReorderableList.ReorderableListEventStruct { DroppedObject = _draggingObject.gameObject, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject, FromList = _reorderableList, FromIndex = _fromIndex }); } else { CancelDrag(); } //If there is no more room for the element in the target list, notify it (OnElementDroppedWithMaxItems event) if (_currentReorderableListRaycasted != null) { if ((_currentReorderableListRaycasted.Content.childCount >= _currentReorderableListRaycasted.maxItems && !_currentReorderableListRaycasted.IsDisplacable) || _currentReorderableListRaycasted.maxItems <= 0) { GameObject o = _draggingObject.gameObject; _reorderableList.OnElementDroppedWithMaxItems.Invoke( new ReorderableList.ReorderableListEventStruct { DroppedObject = o, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : o, FromList = _reorderableList, ToList = _currentReorderableListRaycasted, FromIndex = _fromIndex }); } } } } //Delete fake element if (_fakeElement != null) { Destroy(_fakeElement.gameObject); _fakeElement = null; } _canvasGroup.blocksRaycasts = true; } #endregion void CancelDrag() { _isDragging = false; //If it's a clone, delete it if (_reorderableList.CloneDraggedObject) { Destroy(_draggingObject.gameObject); } //Else replace the draggedObject to his first place else { RefreshSizes(); _draggingObject.SetParent(_reorderableList.Content, false); _draggingObject.rotation = _reorderableList.Content.transform.rotation; _draggingObject.SetSiblingIndex(_fromIndex); var args = new ReorderableList.ReorderableListEventStruct { DroppedObject = _draggingObject.gameObject, IsAClone = _reorderableList.CloneDraggedObject, SourceObject = _reorderableList.CloneDraggedObject ? gameObject : _draggingObject.gameObject, FromList = _reorderableList, FromIndex = _fromIndex, ToList = _reorderableList, ToIndex = _fromIndex }; _reorderableList.Refresh(); _reorderableList.OnElementAdded.Invoke(args); if (!isValid) throw new Exception("Transfer is already Canceled."); } //Delete fake element if (_fakeElement != null) { Destroy(_fakeElement.gameObject); _fakeElement = null; } if (_displacedObject != null) { revertDisplacedElement(); } _canvasGroup.blocksRaycasts = true; } private void RefreshSizes() { Vector2 size = _draggingObjectOriginalSize; if (_currentReorderableListRaycasted != null && _currentReorderableListRaycasted.IsDropable && _currentReorderableListRaycasted.Content.childCount > 0 && _currentReorderableListRaycasted.EqualizeSizesOnDrag) { var firstChild = _currentReorderableListRaycasted.Content.GetChild(0); if (firstChild != null) { size = firstChild.GetComponent<RectTransform>().rect.size; } } _draggingObject.sizeDelta = size; _fakeElementLE.preferredHeight = _draggingObjectLE.preferredHeight = size.y; _fakeElementLE.preferredWidth = _draggingObjectLE.preferredWidth = size.x; _fakeElement.GetComponent<RectTransform>().sizeDelta = size; } public void Init(ReorderableList reorderableList) { _reorderableList = reorderableList; _rect = GetComponent<RectTransform>(); _canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>(); } } }
Hi, When I setup the Re-orderable Vertical ScrollRect, All the elements shrink each time they are released from dragging. Anyone know the solution? I tried loading it at runtime and it works well with Constant Pixel Size in Canvas Settings
Cheers to you, Ziboo, and everyone else who may have had a hand in this. Thank you for sharing! This is incredible.
Thank you so much for this great extension! I have found a little visual bug. Neighbour objects flicker for a split second when starting to drag an object. I've created a new project with the latest uiextensions pack and added a horizontal ReorderableList via GameObject menu and it's happening quite often. It doesn't interfere with the functionality but it looks glitchy. I looked at the "ReorderableListElement" script and I think it's because the neighbour objects reorder themselves during the frame when the fake object is not placed on the content panel yet, but the dragged object is gone at this point. Does anyone had this issue and solved it somehow, or does anyone have an idea what could be done? I'm using Unity 2022.3.31f1 with UIExtensions 2019.6
I'm also experiencing this bug. I have not found a fix/workaround yet. I'm suspicious it is because I'm using it in conjunction with a ContentSizeFitter but I haven't tested this yet. If I find a solution, I'll definitely share here though.
It happens in a fresh project with the default ReorderableList setup from Ziboo, which has a ContentSizeFitter. I have tried all possible combinations of settings on the fitter, LayoutGroup, LayoutElements, etc. to no avail. If I understood the ReorderableListElement script well enough I would fix it and post the script here, but I just can't wrap my head around it.
Yeah, it's definitely not the most readable code. For some reason, removing the RectTransform that I had assigned to the ReorderableList.DraggableArea field did the trick for me. If it's null, it defaults to using the root Canvas RectTransform. I had assigned a parent UI element's RT. But as soon as I removed it, the glitchy behavior stopped.
I'm glad you got it solved! Unfortunately after removing the DraggableArea from the field, it is still glitchy for me. The most depressing part is, that this happens out of the box, in my case at least. What I did: - Create new URP project - Import UIExtensions 2019-6 package via "Assets > Import Package > Custom Package..." - Create Screen Space Overlay Canvas - Add Reorderable Horizontal List template from "GameObject > UI > Extensions" menu - Hit Play The behaviour I observed: When dragging the first (green) square, no matter how many neighbour squares there are, there is no issue: When dragging any other square but the first, all squares to the right will move one slot to the left and back, during the first OnDrag frame I guess, which looks like this when I drag the second (red) square: I guess I'll have another look at the ReorderableListElement code. Ideas are extremely welcome!
I was also experiencing this problem, and I found a solution. It's probably not the best one, but it works. I've narrowed down the problem using Gizmos to the last "else" on the "OnDrag" function in ReoderableListElement. Every time the function is called, the object's parent gets recalculated and the smallest change in movement can trigger a reordering. It's a bit complicated and I don't understand it fully, but since we know the change is happening there and it doesn't need to happen that often, we can put a simple timer to prevent it from happening every frame. So, at the top, I created a bool called "canChange", and set it to true by default. In the said else function, I created a condition for that bool, like so: Code (CSharp): else { if (canChange) { canChange = false; StartCoroutine(ChangeCountdown()); Then the coroutine is simply Code (CSharp): private IEnumerator ChangeCountdown() { yield return new WaitForSeconds(.2f); canChange = true; } You can tweak the delay to what feels best for you. Hope that helped!
I'm having the same problem. The reason is that the LayoutGroup is not updated immediately within the frame where the FakeElement is added. So the solution is to force rebuild layout while setting the parent of the FakeElement It's pretty simple. Find this code in OnDrag method of ReorderableListElement.cs Code (CSharp): if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content) { _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false); } And change it into this one: Code (CSharp): if (_currentReorderableListRaycasted.Content.childCount < _currentReorderableListRaycasted.maxItems && _fakeElement.parent != _currentReorderableListRaycasted.Content) { _fakeElement.SetParent(_currentReorderableListRaycasted.Content, false); LayoutRebuilder.ForceRebuildLayoutImmediate(_currentReorderableListRaycasted.ContentLayout.transform as RectTransform); // Add this line to force rebuild layout }