UGUI’s EventTriggerListener encapsulates various events such as long press, double click, click, drag and drop

Time:2022-11-21

Friends who have used NGUI know that there is aEventTriggerListenerVery easy to use, directly useEventTriggerListener.Get(xxx)You can easily register various events.

Let’s also implement a UGUIEventTriggerListener, and implement common events such as double-click, long-press, etc.

Observing the source code of UGUI shows that UGUI events are implemented through various interfaces provided in EventSystems, so if you want to encapsulate these events, you also need to implement these interfaces.

All of these interfaces are listed below

1. IPointerClickHandler click interface
2. IPointerDownHandler mouse down
3. IPointerUpHandler mouse up
4. IPointerEnterHandler mouse enters
5. IPointerExitHandler mouse left
6. ISelectHandler selected
7. IDeselectHandler Unselect
8. IUpdateSelectedHandler update selected
9. IBeginDragHandler drag and drop start
10. IDragHandler dragging
11. IEndDragHandler drag end
12. IDropHandler drag end (priority>IEndDragHandler)
13. IScrollHandler scrolling
14. IMoveHandler mouse movement

After understanding the function of the event interface, the EventTriggerListener class can be implemented next:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class EventTriggerListener :
MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerEnterHandler,
IPointerExitHandler,
IPointerUpHandler,
ISelectHandler,
IUpdateSelectedHandler,
IDeselectHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IDropHandler,
IScrollHandler,
IMoveHandler
{
    public void OnPointerClick(PointerEventData eventData{}
    public void OnPointerDown(PointerEventData eventData){}
    public void OnPointerUp(PointerEventData eventData){}
    public void OnPointerEnter(PointerEventData eventData{}
    public void OnPointerExit(PointerEventData eventData){}
    public void OnSelect(BaseEventData eventData){}
    public void OnUpdateSelected(BaseEventData eventData){}
    public void OnDeselect(BaseEventData eventData){}
    public void OnBeginDrag(PointerEventData eventData){}
    public void OnDrag(PointerEventData eventData){}
    public void OnEndDrag(PointerEventData eventData){}
    public void OnDrop(PointerEventData eventData){}
    public void OnScroll(PointerEventData eventData){}
    public void OnMove(AxisEventData eventData){}
}

in NGUIEventTriggerListenerEventTriggerListener.Get(xxx) is used to register events, so this method is also used here, that is, use:
EventTriggerListner.Get(xxx).onClick.AddListener();
EventTriggerListner.Get(xxx).onDrag.AddListener();

The way to register various events, and in addition to the above interface, but also to achieve double-click, long-press and other events that UGUI does not provide.

Therefore, it is necessary to design an event transfer station, which provides
AddListener()RemoveListener()RemoveAllListener()
And so on, when the UGUI interface above is triggered, we will trigger a series of events registered externally through this transfer station. It can also be used to implement new events such as double-click and long-press.

Through observation, it is found that when the UGUI event interface is triggered, parameters such as PointerEventData, BaseEventData, and AxisEventData will be provided.
What are these parameters for? It can be found in the Unity documentation that they are the encapsulation of the coordinates of the mouse, the scrolling speed of the object, the number of clicks, etc. when the event is triggered.

Then it should also be passed into the event transfer station for external registration events to use, so this transfer should also be a generic class, and the generic constraint is a class that inherits BaseEventData.

So far in the analysis, the whole process is very clear, let’s start to realize the transfer station.
First design a delegate prototype, which is the prototype of all external registration events and transfer station events:
public delegate void UIEventHandle<T>(GameObject go, T eventData) where T : BaseEventData;

Then the transfer station I named it UIEvent

public class UIEvent<T> where T : BaseEventData
{
    public UIEvent() { }
    public void AddListener(UIEventHandle<T> handle)
    {
        m_UIEventHandle += handle;
    }
    
    public void RemoveListener(UIEventHandle<T> handle)
    {
        m_UIEventHandle -= handle;
    }
    
    public void RemoveAllListeners()
    {
        m_UIEventHandle -= m_UIEventHandle;
        m_UIEventHandle = null;
    }
    
    public void Invoke(GameObject go, T eventData)
    {
        m_UIEventHandle?.Invoke(go, eventData);
    }
    
    private event UIEventHandle<T> m_UIEventHandle = null;
}

At this point, the things we need to prepare are almost written, and then we will return to EventTriggerListener to realize various events

First, create a transit interface for each event

public UIEvent<PointerEventData> onClick = new UIEvent<PointerEventData>()
public UIEvent<PointerEventData> onUp = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onDown = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onEnter = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onExit = new UIEvent<PointerEventData>();
public UIEvent<BaseEventData> onSelect = new UIEvent<BaseEventData>();
public UIEvent<BaseEventData> onUpdateSelect = new UIEvent<BaseEventData>();
public UIEvent<BaseEventData> onDeselect = new UIEvent<BaseEventData>();
public UIEvent<PointerEventData> onBeginDrag = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onDrag = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onEndDrag = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onDrop = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onScroll = new UIEvent<PointerEventData>();
public UIEvent<AxisEventData> onMove = new UIEvent<AxisEventData>();

Then, put them into the UGUI event interface

public void OnPointerEnter(PointerEventData eventData) { onEnter.Invoke(gameObject, eventData); }
public void OnPointerExit(PointerEventData eventData) { onExit.Invoke(gameObject, eventData); }
public void OnSelect(BaseEventData eventData) { onSelect.Invoke(gameObject, eventData); }
public void OnUpdateSelected(BaseEventData eventData) { onUpdateSelect.Invoke(gameObject, eventData); }
public void OnDeselect(BaseEventData eventData) { onDeselect.Invoke(gameObject, eventData); }
public void OnBeginDrag(PointerEventData eventData) { onBeginDrag.Invoke(gameObject, eventData); }
public void OnDrag(PointerEventData eventData) { onDrag.Invoke(gameObject, eventData); }
public void OnEndDrag(PointerEventData eventData) { onEndDrag.Invoke(gameObject, eventData); }
public void OnDrop(PointerEventData eventData) { onDrop.Invoke(gameObject, eventData); }
public void OnScroll(PointerEventData eventData) { onScroll.Invoke(gameObject, eventData); }
public void OnMove(AxisEventData eventData) { onMove.Invoke(gameObject, eventData); }

At this point, except for double-click and long-press, other events are encapsulated, first test whether it can be used as conveniently as the analysis at the beginning

Test code:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public Button btn;
    private void Awake()
    {
        EventTriggerListener.Get(btn.gameObject).onClick.AddListener(onClick);
        EventTriggerListener.Get(btn.gameObject).onBeginDrag.AddListener(onBeginDrag);
        EventTriggerListener.Get(btn.gameObject).onDrag.AddListener(onDrag);
        EventTriggerListener.Get(btn.gameObject).onEndDrag.AddListener(onEndDrag);
    }

    private void onClick(GameObject go, PointerEventData eventData)
    {
        Debug.Log("OnClcik");
    }

    private void onBeginDrag(GameObject go, PointerEventData eventData)
    {
        Debug.Log("OnBeginDrag");
    }

    private void onDrag(GameObject go, PointerEventData eventData)
    {
        Debug.Log("onDrag");
    }

    private void onEndDrag(GameObject go, PointerEventData eventData)
    {
        Debug.Log("OnEndDrag");
    }
}

Test Results:
UGUI's EventTriggerListener encapsulates various events such as long press, double click, click, drag and drop
The test method is to click and drag a Button, the printed result is the same as expected, and then double-click and long-press are realized.

Similarly, create a transit interface for these two events:

public UIEvent<PointerEventData> onDoubleClick = new UIEvent<PointerEventData>();
public UIEvent<PointerEventData> onPress = new UIEvent<PointerEventData>();

Let’s first analyze how to achieve
1. The three events of long press, double click and single click should be mutually exclusive. That is to say, if the long press is triggered, the double-click and single-click should not be triggered, and the other two will not be triggered if the double-click is triggered, and so on.

2. Trigger conditions

Trigger condition for long press: press the mouse for a certain period of time
Trigger conditions for double-click: no long-press is triggered, and the interval between mouse lifts does not exceed a certain time
Click trigger conditions: no long press is triggered, the timing starts after the mouse is lifted for the first time until the double-click trigger time is exceeded

It involves countdown, which obviously needs to be detected in Update, and the event trigger added in OnPoinetClick before should also be deleted, so that it can be triggered at the right time.

Trigger time Here, after several experiments, I feel that the long-press time of 0.5 seconds and the double-click time of 0.2 seconds are more appropriate.

private const float DOUBLE_CLICK_TIME = 0.2f;
private const float PRESS_TIME = 0.5f;

private float m_CurrDonwTime = 0f;
private bool m_IsPointDown = false;
private bool m_IsPress = false;
private int m_ClickCount = 0;
private PointerEventData m_OnUpEventData = null;

private void Update()
{
    if (m_IsPointDown)
    {
        if (Time.unscaledTime - m_CurrDonwTime >= PRESS_TIME)
        {
            m_IsPress = true;
            m_IsPointDown = false;
            m_CurrDonwTime = 0f;
            onPress.Invoke(gameObject, null);
        }
    }

    if (m_ClickCount > 0)
    {
        if (Time.unscaledTime - m_CurrDonwTime >= DOUBLE_CLICK_TIME)
        {
            if (m_ClickCount < 2)
            {
                onUp.Invoke(gameObject, m_OnUpEventData);
                onClick.Invoke(gameObject, m_OnUpEventData);
                m_OnUpEventData = null;
            }
            m_ClickCount = 0;
        }

        if (m_ClickCount >= 2)
        {
            onDoubleClick.Invoke(gameObject, m_OnUpEventData);
            m_OnUpEventData = null;
            m_ClickCount = 0;
        }
    }
}

public void OnPointerClick(PointerEventData eventData)
{

}

public void OnPointerDown(PointerEventData eventData)
{
    m_IsPointDown = true;
    m_IsPress = false;
    m_CurrDonwTime = Time.unscaledTime;
    onDown?.Invoke(gameObject, eventData);
}

public void OnPointerUp(PointerEventData eventData)
{
    m_IsPointDown = false;
    m_OnUpEventData = eventData;
    if (!m_IsPress)
    {
        m_ClickCount++;
    }
}

Here is a tip, try not to use Time.deltaTime accumulation for timing, because the execution time of each frame on different devices is not the same, when a more accurate time is required, this accumulation method will produce errors, so it is best to use time stamp.

At this point, the entire class is basically implemented. Change the events in the above test code to single-click, double-click, and long-press to test the newly added single-click, double-click, and long-press to see if there is any problem.

Click on:
UGUI's EventTriggerListener encapsulates various events such as long press, double click, click, drag and drop
double click
UGUI's EventTriggerListener encapsulates various events such as long press, double click, click, drag and drop
Press
UGUI's EventTriggerListener encapsulates various events such as long press, double click, click, drag and drop
It can be seen that no click is triggered when a double-click is triggered, and no click is triggered when a long press is triggered

Finally, the entire EventTriggerListener code is as follows

using UnityEngine;
using UnityEngine.EventSystems;

public class EventTriggerListener :
MonoBehaviour,
IPointerClickHandler,
IPointerDownHandler,
IPointerEnterHandler,
IPointerExitHandler,
IPointerUpHandler,
ISelectHandler,
IUpdateSelectedHandler,
IDeselectHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IDropHandler,
IScrollHandler,
IMoveHandler
{
    public delegate void UIEventHandle<T>(GameObject go, T eventData) where T : BaseEventData;

    public class UIEvent<T> where T : BaseEventData
    {
        public UIEvent() { }

        public void AddListener(UIEventHandle<T> handle)
        {
            m_UIEventHandle += handle;
        }

        public void RemoveListener(UIEventHandle<T> handle)
        {
            m_UIEventHandle -= handle;
        }

        public void RemoveAllListeners()
        {
            m_UIEventHandle -= m_UIEventHandle;
            m_UIEventHandle = null;
        }

        public void Invoke(GameObject go, T eventData)
        {
            m_UIEventHandle?.Invoke(go, eventData);
        }

        private event UIEventHandle<T> m_UIEventHandle = null;
    }


    public UIEvent<PointerEventData> onClick = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onDoubleClick = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onPress = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onUp = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onDown = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onEnter = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onExit = new UIEvent<PointerEventData>();
    public UIEvent<BaseEventData> onSelect = new UIEvent<BaseEventData>();
    public UIEvent<BaseEventData> onUpdateSelect = new UIEvent<BaseEventData>();
    public UIEvent<BaseEventData> onDeselect = new UIEvent<BaseEventData>();
    public UIEvent<PointerEventData> onBeginDrag = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onDrag = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onEndDrag = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onDrop = new UIEvent<PointerEventData>();
    public UIEvent<PointerEventData> onScroll = new UIEvent<PointerEventData>();
    public UIEvent<AxisEventData> onMove = new UIEvent<AxisEventData>();

    public static EventTriggerListener Get(GameObject go)
    {
        if (go == null)
        {
            return null;
        }
        EventTriggerListener eventTrigger = go.GetComponent<EventTriggerListener>();
        if (eventTrigger == null) eventTrigger = go.AddComponent<EventTriggerListener>();
        return eventTrigger;
    }

    private void Update()
    {
        if (m_IsPointDown)
        {
            if (Time.unscaledTime - m_CurrDonwTime >= PRESS_TIME)
            {
                m_IsPress = true;
                m_IsPointDown = false;
                m_CurrDonwTime = 0f;
                onPress.Invoke(gameObject, null);
            }
        }

        if (m_ClickCount > 0)
        {
            if (Time.unscaledTime - m_CurrDonwTime >= DOUBLE_CLICK_TIME)
            {
                if (m_ClickCount < 2)
                {
                    onUp.Invoke(gameObject, m_OnUpEventData);
                    onClick.Invoke(gameObject, m_OnUpEventData);
                    m_OnUpEventData = null;
                }
                m_ClickCount = 0;
            }

            if (m_ClickCount >= 2)
            {
                onDoubleClick.Invoke(gameObject, m_OnUpEventData);
                m_OnUpEventData = null;
                m_ClickCount = 0;
            }
        }
    }

    private void OnDestroy()
    {
        RemoveAllListeners();
    }

    public void RemoveAllListeners()
    {
        onClick.RemoveAllListeners();
        onDoubleClick.RemoveAllListeners();
        onDown.RemoveAllListeners();
        onEnter.RemoveAllListeners();
        onExit.RemoveAllListeners();
        onUp.RemoveAllListeners();
        onSelect.RemoveAllListeners();
        onUpdateSelect.RemoveAllListeners();
        onDeselect.RemoveAllListeners();
        onDrag.RemoveAllListeners();
        onEndDrag.RemoveAllListeners();
        onDrop.RemoveAllListeners();
        onScroll.RemoveAllListeners();
        onMove.RemoveAllListeners();
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        m_IsPointDown = true;
        m_IsPress = false;
        m_CurrDonwTime = Time.unscaledTime;
        onDown?.Invoke(gameObject, eventData);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        m_IsPointDown = false;
        m_OnUpEventData = eventData;
        if (!m_IsPress)
        {
            m_ClickCount++;
        }
    }

    public void OnPointerClick(PointerEventData eventData{}
    public void OnPointerEnter(PointerEventData eventData) { onEnter.Invoke(gameObject, eventData); }
    public void OnPointerExit(PointerEventData eventData) { onExit.Invoke(gameObject, eventData); }
    public void OnSelect(BaseEventData eventData) { onSelect.Invoke(gameObject, eventData); }
    public void OnUpdateSelected(BaseEventData eventData) { onUpdateSelect.Invoke(gameObject, eventData); }
    public void OnDeselect(BaseEventData eventData) { onDeselect.Invoke(gameObject, eventData); }
    public void OnBeginDrag(PointerEventData eventData) { onBeginDrag.Invoke(gameObject, eventData); }
    public void OnDrag(PointerEventData eventData) { onDrag.Invoke(gameObject, eventData); }
    public void OnEndDrag(PointerEventData eventData) { onEndDrag.Invoke(gameObject, eventData); }
    public void OnDrop(PointerEventData eventData) { onDrop.Invoke(gameObject, eventData); }
    public void OnScroll(PointerEventData eventData) { onScroll.Invoke(gameObject, eventData); }
    public void OnMove(AxisEventData eventData) { onMove.Invoke(gameObject, eventData); }

    private const float DOUBLE_CLICK_TIME = 0.2f;
    private const float PRESS_TIME = 0.5f;

    private float m_CurrDonwTime = 0f;
    private bool m_IsPointDown = false;
    private bool m_IsPress = false;
    private int m_ClickCount = 0;
    private PointerEventData m_OnUpEventData = null;
}

OK, here is U3D Mengxin, goodbye.