Перетаскивание в ScrollRect (ScrollView) в Unity3D

Я хочу реализовать перетаскивание содержимого прокрутки.

Проблема в том, что когда вы пытаетесь перетаскивать элементы в режиме прокрутки, вы не можете прокручивать вид.

Во-первых, я попытался реализовать перетаскивание с помощью интерфейсов IDragHandler, IBeginDragHandler, IEndDragHandle и IDropHandler. На первый взгляд, это работало довольно хорошо, но проблема заключалась в том, что вы не можете прокручивать ScrollRect.

Я думаю, что проблема связана с переопределением. Когда я использую триггеры событий, такие же, как прямоугольник прокрутки, например перетаскивание, родительский не работает должным образом.

Итак, после этого я подумал сам и реализовал его с помощью интерфейсов IPointerDown, IPointerUp и определенного времени для удержания перетаскиваемого пользовательского интерфейса в ScrollRect, и если вы не удерживаете его в определенное время, прокрутка работает хорошо.

Но проблема в том, что включение скрипта DragHandler, который я написал до того, как функции OnDrag, OnBeginDrag и OnEndDrag не работают, когда время удержания истекло.

Сначала я хочу знать, есть ли способ вызвать эти функции?

Во-вторых, есть ли способ реализовать пользовательский интерфейс перетаскивания без использования интерфейсов перетаскивания?

Перетаскивание:

using System;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragHandler : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    public static GameObject itemBeingDragged;

    private Vector3 startPos;

    private Transform startParent;

    DragHandler dragHandler;

    public void Awake()
    {
        dragHandler = GetComponent<DragHandler>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("Begin");
        itemBeingDragged = gameObject;
        startPos = transform.position;
        startParent = transform.parent;
    }

    public void OnDrag(PointerEventData eventData)
    {
        Debug.Log("Drag");
        transform.position = Input.mousePosition;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("End");
        itemBeingDragged = null;
        if (transform.parent == startParent)
        {
            dragHandler.enabled = false;
            transform.SetParent(startParent);
            transform.position = startPos;
        }
    }
}

Скроллректконтроллер:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ScrollRectController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    public float holdTime;
    public float maxVelocity;

    private Transform scrollRectParent;

    private DragHandler dragHandler;

    private ScrollRect scrollRect;

    private float timer;

    private bool isHolding;

    void Awake()
    {
        scrollRectParent = GameObject.FindGameObjectWithTag("rec_dlg").transform;
        dragHandler = GetComponent<DragHandler>();
        dragHandler.enabled = false;
    }

    // Use this for initialization
    void Start()
    {
        timer = holdTime;
    }

    // Update is called once per frame
    void Update()
    {

    }

    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.Log("Down");
        scrollRect = scrollRectParent.GetComponent<ScrollRect>();
        isHolding = true;
        StartCoroutine(Holding());
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("Up");
        isHolding = false;
    }

    IEnumerator Holding()
    {
        while (timer > 0)
        {
            //if (scrollRect.velocity.x >= maxVelocity)
            //{
            //    isHolding = false;
            //}

            if (!isHolding)
            {
                timer = holdTime;
                yield break;
            }

            timer -= Time.deltaTime;
            Debug.Log(timer);
            yield return null;
        }

        dragHandler.enabled = true;
        //dragHandler.OnBeginDrag();
    }
}

Слот:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class Slot : MonoBehaviour, IDropHandler
{
    public void OnDrop(PointerEventData eventData)
    {
        DragHandler.itemBeingDragged.transform.SetParent(transform);
    }
}

person ATHellboy    schedule 13.06.2017    source источник
comment
ФУ. Перетаскивание элементов пользовательского интерфейса — это огромная головная боль в новом пользовательском интерфейсе. Это единственное, что непросто. У меня нет проекта на этом компьютере, который использует его прямо сейчас, но, по сути, вы создаете представление прокрутки нулевого размера для каждого перетаскиваемого элемента с режимом зажима, установленным на несвязанный. Затем эти представления прокрутки встраиваются в родительский контейнер (который может быть другим представлением прокрутки) в точке привязки. Преобразование объекта содержимого представления прокрутки для каждого элемента используется для позиционирования. IIRC. Если хотите, загляните на github.com/Draco18s/IdleArtificer.   -  person Draco18s no longer trusts SE    schedule 13.06.2017
comment
Эй, я проверил ссылку и скачал проект, покопался в нем, но не нашел ничего полезного и связанного с перетаскиванием в режиме прокрутки, я думаю, мне нужно кое-что уточнить, насчет перетаскивания в режиме прокрутки, я означает перетаскивание элемента из режима прокрутки в другое место вне режима прокрутки. О совете, который вы сказали, я не думаю, что он решает мою проблему.   -  person ATHellboy    schedule 13.06.2017
comment
Извини. У меня закончились персонажи. Вам нужно скачать его. Откройте основную сцену. Слева от видимой области игры я держу копии префабов. Ищите тот, у которого есть стрелка на/рядом с ним. Этот экземпляр, созданный во время игры, можно перетаскивать. Вы захотите изучить иерархию преобразований (включая опорную точку и привязки RectTransform). Они создаются CraftingManager.cs, строка 210, а 219 управляет тем, где он размещается на экране.   -  person Draco18s no longer trusts SE    schedule 13.06.2017
comment
Эй, чувак, я использовал сборку BuildingGridItem в твоем проекте. Я добавил кучу этого префаба в представление прокрутки и протестировал перетаскивание и прокрутку, но проблема в том, что когда указатель мыши находится на BuildingGridItem, представление прокрутки прокрутки не работает так же, как моя проблема.   -  person ATHellboy    schedule 13.06.2017
comment
Вы можете либо перетащить элемент вокруг или прокрутить родительский вид прокрутки. Вы не можете делать и то, и другое одновременно, это не имеет смысла.   -  person Draco18s no longer trusts SE    schedule 13.06.2017
comment
Да, но пространство моего представления прокрутки очень мало, чтобы просто выбрать эти пробелы и прокрутить его, поэтому я думаю, что сначала удерживайте перетаскиваемый элемент, после чего можно перетащить его, и если вы не удерживаете элемент, вы можете прокручивать представление прокрутки, мои коды, которые я отправил в своем первом сообщении, показывают это, но проблема в том, что я не могу управлять функцией OnDrag после окончания времени удержания. Если вы понимаете, о чем я ?   -  person ATHellboy    schedule 14.06.2017
comment
В этот момент вы вступаете в очень сложное поведение, которое наивно не поддерживается. т.е. вам нужно понять это для себя.   -  person Draco18s no longer trusts SE    schedule 14.06.2017
comment
Да, поэтому я пытаюсь спросить других, кто знает, как справиться с этой проблемой, потому что я не думаю, что это не было сделано раньше кем-то.   -  person ATHellboy    schedule 14.06.2017


Ответы (3)


Ответ:

Я написал несколько кодов, которые обрабатывают перетаскивание в scrollRect(scrollView) без использования интерфейсов DragHandler.

Перетаскивание:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DragHandler : MonoBehaviour, IPointerExitHandler
{
    public static GameObject itemBeingDragged;

    public static bool isCustomerDragged;

    public Transform customerScrollRect;
    public Transform dragParent;

    public float holdTime;
    public float maxScrollVelocityInDrag;

    private Transform startParent;

    private ScrollRect scrollRect;

    private float timer;

    private bool isHolding;
    private bool canDrag;
    private bool isPointerOverGameObject;

    private CanvasGroup canvasGroup;

    private Vector3 startPos;

    public Transform StartParent
    {
        get { return startParent; }
    }

    public Vector3 StartPos
    {
        get { return startPos; }
    }

    void Awake()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }

    // Use this for initialization
    void Start()
    {
        timer = holdTime;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (EventSystem.current.currentSelectedGameObject == gameObject)
            {
                //Debug.Log("Mouse Button Down");
                scrollRect = customerScrollRect.GetComponent<ScrollRect>();
                isPointerOverGameObject = true;
                isHolding = true;
                StartCoroutine(Holding());
            }
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (EventSystem.current.currentSelectedGameObject == gameObject)
            {
                //Debug.Log("Mouse Button Up");
                isHolding = false;

                if (canDrag)
                {
                    itemBeingDragged = null;
                    isCustomerDragged = false;
                    if (transform.parent == dragParent)
                    {
                        canvasGroup.blocksRaycasts = true;
                        transform.SetParent(startParent);
                        transform.localPosition = startPos;
                    }
                    canDrag = false;
                    timer = holdTime;
                }
            }
        }

        if (Input.GetMouseButton(0))
        {
            if (EventSystem.current.currentSelectedGameObject == gameObject)
            {
                if (canDrag)
                {
                    //Debug.Log("Mouse Button");
                    transform.position = Input.mousePosition;
                }
                else
                {
                    if (!isPointerOverGameObject)
                    {
                        isHolding = false;
                    }
                }
            }
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        isPointerOverGameObject = false;
    }

    IEnumerator Holding()
    {
        while (timer > 0)
        {
            if (scrollRect.velocity.x >= maxScrollVelocityInDrag)
            {
                isHolding = false;
            }

            if (!isHolding)
            {
                timer = holdTime;
                yield break;
            }

            timer -= Time.deltaTime;
            //Debug.Log("Time : " + timer);
            yield return null;
        }

        isCustomerDragged = true;
        itemBeingDragged = gameObject;
        startPos = transform.localPosition;
        startParent = transform.parent;
        canDrag = true;
        canvasGroup.blocksRaycasts = false;
        transform.SetParent(dragParent);
    }

    public void Reset()
    {
        isHolding = false;
        canDrag = false;
        isPointerOverGameObject = false;
    }
}

Некоторое объяснение этого фрагмента кода:

  1. Вашему перетаскиваемому элементу пользовательского интерфейса нужна неразрешимая опция, для меня я использовал кнопку.
  2. Вам нужно прикрепить этот скрипт к перетаскиваемому элементу.
  3. Также вам необходимо добавить компонент Canvas Group.
  4. customerScrollRect является родителем ScrollRect ваших элементов.
  5. dragParent может быть пустым GameObject, который используется из-за маски порта просмотра.

Слот:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    bool isEntered;

    public void OnPointerEnter(PointerEventData eventData)
    {
        isEntered = true;
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        isEntered = false;
    }

    void Update()
    {
        if (Input.GetMouseButtonUp(0))
        {
            if (isEntered)
            {
                if (DragHandler.itemBeingDragged)
                {
                    GameObject draggedItem = DragHandler.itemBeingDragged;
                    DragHandler dragHandler = draggedItem.GetComponent<DragHandler>();
                    Vector3 childPos = draggedItem.transform.position;
                    //Debug.Log("On Pointer Enter");
                    draggedItem.transform.SetParent(dragHandler.StartParent);
                    draggedItem.transform.localPosition = dragHandler.StartPos;
                    draggedItem.transform.parent.SetParent(transform);
                    draggedItem.transform.parent.position = childPos;
                    isEntered = false;
                }
            }
        }
    }
}

Некоторое объяснение этого скрипта:

1.Прикрепите скрипт к выпавшему предмету.

person ATHellboy    schedule 15.06.2017
comment
Этот ответ очень полезен! Одна вещь, которую можно было бы улучшить, - это сохранение/восстановление вашего childIndex, чтобы элемент не попадал в нижнюю часть scrollRect. - person Foxor; 21.04.2018

Самое простое решение этой проблемы — вручную вызвать события ScrollRect, IF пользователь не нажимал достаточно долго, используя ExecuteEvents.Execute. Это решение содержит наименьшее количество дополнительного кода.

using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DragAndDropHandler : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    public bool Draggable { get; set; }

    private bool draggingSlot;

    [SerializeField] private ScrollRect scrollRect;

    public void OnPointerDown(PointerEventData eventData)
    {
        if (!Draggable)
        {
            return;
        }

        StartCoroutine(StartTimer());
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        StopAllCoroutines();
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        StopAllCoroutines();
    }

    private IEnumerator StartTimer()
    {
        yield return new WaitForSeconds(0.5f);
        draggingSlot = true;
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(scrollRect.gameObject, eventData, ExecuteEvents.beginDragHandler);
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (draggingSlot)
        {
            //DO YOUR DRAGGING HERE
        } else
        {
            //OR DO THE SCROLLRECT'S
            ExecuteEvents.Execute(scrollRect.gameObject, eventData, ExecuteEvents.dragHandler);
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(scrollRect.gameObject, eventData, ExecuteEvents.endDragHandler);
        if (draggingSlot)
        {
            //END YOUR DRAGGING HERE
            draggingSlot = false;
        }
    }
}
person wilbil741    schedule 08.05.2020
comment
Использование ExecuteEvents — блестящий ответ! Также упрощает выборочное включение и выключение перетаскивания прокрутки (я хотел перетаскивать элементы из прямоугольника прокрутки в свою сцену). Обратите внимание, что устаревшая документация кажется более полезной, чем последняя документация. - person idbrii; 20.08.2020

Мне удалось найти более простое решение. Возможно, он должен быть настроен под особые потребности, но если вы поместите этот скрипт на свои предметы и сделаете настройку, он должен работать.

Это не так просто из сборки, но я позволил контроллеру сделать это. Я нахожу все UIElementDragger внутри указанного GameObject и программно добавляю необходимые экземпляры GO.

Но можете использовать этот код из коробки, если не используете префабы.

using UnityEngine;
using UnityEngine.EventSystems;

public class UIElementDragger : MonoBehaviour, IPointerUpHandler, IPointerDownHandler
{
    /// <summary>
    /// Offset in pixels horizontally (positive to right, negative to left)
    /// </summary>
    [Range(40, 100)]
    public float offsetX = 40;

    /// <summary>
    /// Offset in pixels vertically (positive to right, negative to left)
    /// </summary>
    [Range(40, 100)]
    public float offsetY = 40;

    /// <summary>
    /// The Panel where the item will set as Child to during drag
    /// </summary>
    public Transform parentRect;

    /// <summary>
    /// The GameObject where the item is at start
    /// </summary>
    public Transform homeWrapper;

    /// <summary>
    /// The Object where the mouse must be when pointer is up, to put it in this panel
    /// </summary>
    public Transform targetRect;

    /// <summary>
    /// The GameObject where the item should live after dropping
    /// </summary>
    public Transform targetWrapper;

    private int siblingIndex;
    private bool dragging;

    private void Start()
    {
        siblingIndex = transform.GetSiblingIndex();
    }

    private void Update()
    {
        if (dragging)
        {
            transform.position = new Vector2(Input.mousePosition.x + offsetX, Input.mousePosition.y + offsetY);
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        transform.parent = parentRect;
        dragging = true;
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        if (eventData.pointerCurrentRaycast.gameObject.transform.IsChildOf(targetRect))
        {
            transform.parent = targetWrapper;
        }
        else
        {
            transform.parent = homeWrapper;
            transform.SetSiblingIndex(siblingIndex);
        }

        dragging = false;
    }
}
person Christian Rosick    schedule 01.07.2019