UGUI的EventTriggerListener,封裝長按、雙擊、點擊、拖拽等多種事件

用過NGUI的夥伴都知道,NGUI中有一個EventTriggerListener很是的好用,直接使用EventTriggerListener.Get(xxx)就能夠方便的對各類事件進行註冊。測試

咱們也來實現一個UGUI的EventTriggerListener,並實現雙擊、長按、等經常使用事件。spa

觀察UGUI的源碼可知UGUI的事件是經過EventSystems中提供的各類接口來實現的,因此要想對這些事件進行封裝,也須要實現這些接口。設計

下面列出所有的這些接口code

1.  IPointerClickHandler    點擊接口
2.  IPointerDownHandler     鼠標按下
3.  IPointerUpHandler       鼠標擡起
4.  IPointerEnterHandler    鼠標進入
5.  IPointerExitHandler     鼠標離開
6.  ISelectHandler          選中
7.  IDeselectHandler        取消選中
8.  IUpdateSelectedHandler  更新選中
9.  IBeginDragHandler       拖拽開始
10. IDragHandler            拖拽中
11. IEndDragHandler         拖拽結束
12. IDropHandler            拖拽結束(優先級>IEndDragHandler)
13. IScrollHandler          滾動
14. IMoveHandler            鼠標移動

瞭解了事件接口的做用,接下來就能夠實現EventTriggerListener類:blog

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){}
}

NGUI中的EventTriggerListener是使用EventTriggerListener.Get(xxx)的方式來註冊事件,因此這裏也使用這種方式,即便用:
EventTriggerListner.Get(xxx).onClick.AddListener();
EventTriggerListner.Get(xxx).onDrag.AddListener();繼承

的方式來註冊各類事件,而且除了上述接口,還要實現雙擊,長按等UGUI沒有提供的事件。接口

所以這裏須要設計一個事件的中轉站,它提供
AddListener()RemoveListener()RemoveAllListener()
等方法,在上面那一坨UGUI接口被觸發時就經過這個中轉站來觸發咱們在外部註冊的一系列事件,它也能夠用來實現雙擊、長按等新事件。事件

而經過觀察發現,UGUI事件接口觸發時都會提供PointerEventData,BaseEventData,AxisEventData,這樣的參數。
這些參數是幹嗎的呢?在Unity文檔中能夠查到,原來它們是對事件觸發時鼠標的座標、物體滾動速度、點擊次數等的封裝。ci

那這裏也要把它傳進事件中轉站,提供給外部註冊事件來使用,所以這個中轉也應該是一個泛型類,且泛型約束爲繼承了BaseEventData的類。文檔

分析至此,整個流程已經很明確,下面開始實現中轉站。
首先設計一個委託原型,它是全部外部註冊事件、中轉站事件的原型:
public delegate void UIEventHandle<T>(GameObject go, T eventData) where T : BaseEventData;

而後中轉站我給它起名字叫作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;
}

到這裏咱們須要準備的東西寫的差很少了,接下來就回到EventTriggerListener中,把各類事件實現出來

首先,建立各個事件的中轉接口

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>();

而後,把它們放到UGUI事件接口中

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); }

到這裏除了雙擊和長按,其餘的事件就都封裝好了,先測試一下是否是能夠像開頭分析的那樣方便的使用它

測試代碼:

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");
    }
}

測試結果:
QQ截圖20200224132250.png
測試方式就是對一個Button進行點擊和拖拽,打印的結果和預想的同樣,接下來實現雙擊和長按。

一樣的,爲這兩個事件也建立中轉接口:

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

先來分析如何實現
1.長按,雙擊,單擊這3個事件理應是互斥的。也就是說觸發了長按那雙擊和單擊就不該該被觸發,而觸發了雙擊其餘2個也不會觸發,以此類推。

2.觸發條件

長按的觸發條件:按下鼠標達到必定時間
雙擊的觸發條件:沒有觸發長按,鼠標擡起兩次的間隔不超過某個時間
單擊的觸發條件:沒有觸發長按,第一次鼠標擡起後開始計時直到超過雙擊觸發時間

涉及倒計時,顯然須要在Update中檢測,並且以前在OnPoinetClick中添加的事件觸發也要刪掉,在合適的時機才能觸發。

觸發時間這裏我通過幾回試驗,感受長按時間爲0.5秒,雙擊時間爲0.2秒是比較合適的。

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++;
    }
}

這裏給一個小貼士,計時儘可能不要使用Time.deltaTime累加,由於不一樣設備上每幀的執行時間不盡相同,在須要比較精確的時間時,這樣的累加方式會產生偏差,因此最好使用時間戳。

到了這裏基本整個類就實現完了,把上面的測試代碼中的事件分別改成單擊,雙擊,長按來測試剛剛新添加的單擊,雙擊,長按,看看有沒有問題。

單擊:
1.png
雙擊
2.png
長按
3.png
能夠看到,在觸發雙擊時沒有觸發單擊,觸發長按時也沒有觸發單擊

最後,整個EventTriggerListener代碼以下

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,這裏是U3D萌新,再見。

相關文章
相關標籤/搜索