用过NGUI的伙伴都知道,NGUI中有一个EventTriggerListener非常的好用,直接使用EventTriggerListener.Get(xxx)
就可以方便的对各种事件进行注册。
我们也来实现一个UGUI的EventTriggerListener,并实现双击、长按、等常用事件。
观察UGUI的源码可知UGUI的事件是通过EventSystems中提供的各种接口来实现的,所以要想对这些事件进行封装,也需要实现这些接口。
下面列出全部的这些接口
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类:
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文档中可以查到,原来它们是对事件触发时鼠标的坐标、物体滚动速度、点击次数等的封装。
那这里也要把它传进事件中转站,提供给外部注册事件来使用,因此这个中转也应该是一个泛型类,且泛型约束为继承了BaseEventData的类。
分析至此,整个流程已经很明确,下面开始实现中转站。
首先设计一个委托原型,它是所有外部注册事件、中转站事件的原型:public delegate void UIEventHandle
然后中转站我给它起名字叫做UIEvent
public class UIEvent where T : BaseEventData
{
public UIEvent() { }
public void AddListener(UIEventHandle handle)
{
m_UIEventHandle += handle;
}
public void RemoveListener(UIEventHandle 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 m_UIEventHandle = null;
}
到这里我们需要准备的东西写的差不多了,接下来就回到EventTriggerListener中,把各种事件实现出来
首先,创建各个事件的中转接口
public UIEvent onClick = new UIEvent()
public UIEvent onUp = new UIEvent();
public UIEvent onDown = new UIEvent();
public UIEvent onEnter = new UIEvent();
public UIEvent onExit = new UIEvent();
public UIEvent onSelect = new UIEvent();
public UIEvent onUpdateSelect = new UIEvent();
public UIEvent onDeselect = new UIEvent();
public UIEvent onBeginDrag = new UIEvent();
public UIEvent onDrag = new UIEvent();
public UIEvent onEndDrag = new UIEvent();
public UIEvent onDrop = new UIEvent();
public UIEvent onScroll = new UIEvent();
public UIEvent onMove = new UIEvent();
然后,把它们放到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");
}
}
测试结果:
测试方式就是对一个Button进行点击和拖拽,打印的结果和预想的一样,接下来实现双击和长按。
同样的,为这两个事件也创建中转接口:
public UIEvent onDoubleClick = new UIEvent();
public UIEvent onPress = new UIEvent();
先来分析如何实现
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累加,因为不同设备上每帧的执行时间不尽相同,在需要比较精确的时间时,这样的累加方式会产生误差,所以最好使用时间戳。
到了这里基本整个类就实现完了,把上面的测试代码中的事件分别改为单击,双击,长按来测试刚刚新添加的单击,双击,长按,看看有没有问题。
单击:
可以看到,在触发双击时没有触发单击,触发长按时也没有触发单击
最后,整个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(GameObject go, T eventData) where T : BaseEventData;
public class UIEvent where T : BaseEventData
{
public UIEvent() { }
public void AddListener(UIEventHandle handle)
{
m_UIEventHandle += handle;
}
public void RemoveListener(UIEventHandle 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 m_UIEventHandle = null;
}
public UIEvent onClick = new UIEvent();
public UIEvent onDoubleClick = new UIEvent();
public UIEvent onPress = new UIEvent();
public UIEvent onUp = new UIEvent();
public UIEvent onDown = new UIEvent();
public UIEvent onEnter = new UIEvent();
public UIEvent onExit = new UIEvent();
public UIEvent onSelect = new UIEvent();
public UIEvent onUpdateSelect = new UIEvent();
public UIEvent onDeselect = new UIEvent();
public UIEvent onBeginDrag = new UIEvent();
public UIEvent onDrag = new UIEvent();
public UIEvent onEndDrag = new UIEvent();
public UIEvent onDrop = new UIEvent();
public UIEvent onScroll = new UIEvent();
public UIEvent onMove = new UIEvent();
public static EventTriggerListener Get(GameObject go)
{
if (go == null)
{
return null;
}
EventTriggerListener eventTrigger = go.GetComponent();
if (eventTrigger == null) eventTrigger = go.AddComponent();
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萌新,再见。