UGUI的EventTriggerListener,封装长按、双击、点击、拖拽等多种事件

裂谷
• 阅读 7456

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

测试结果:
UGUI的EventTriggerListener,封装长按、双击、点击、拖拽等多种事件
测试方式就是对一个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累加,因为不同设备上每帧的执行时间不尽相同,在需要比较精确的时间时,这样的累加方式会产生误差,所以最好使用时间戳。

到了这里基本整个类就实现完了,把上面的测试代码中的事件分别改为单击,双击,长按来测试刚刚新添加的单击,双击,长按,看看有没有问题。

单击:
UGUI的EventTriggerListener,封装长按、双击、点击、拖拽等多种事件
双击
UGUI的EventTriggerListener,封装长按、双击、点击、拖拽等多种事件
长按
UGUI的EventTriggerListener,封装长按、双击、点击、拖拽等多种事件
可以看到,在触发双击时没有触发单击,触发长按时也没有触发单击

最后,整个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萌新,再见。

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
UGUI 自定义滚动选择列表 ListView
列表在游戏的UI中是非常常见的,例如选服页面,商城页面,奖励页面等等都会有列表的存在。文中我们将这些列表称为ListView(类似于fgui的GList),而列表中的每项称作Item。首先我们来分析下,我们的ListView需要实现哪些功能,以及如何实现功能解决思路可以通过滑动来显示ListView中的Item可以使用UGUI的Scrol
Stella981 Stella981
3年前
Guava — EventBus
Guava提供了事件总线的一个实现方案EventBus。它是事件发布订阅模式的实现,观察者模式。Guava为我们提供了同步实现EventBus和异步实现AsyncEventBus两个事件总线,他们都不是单例的eventBus.post(1);eventBus.post(1L);post方法,直接发布事件订阅者需要注册进来,ev
Stella981 Stella981
3年前
RecyclerView 点击事件和长按事件
在Adapter适配器中设置publicvoidonBindViewHolder(Adapter.MyHolderholder,intposition){holder.img.setImageResource(list.get(position).getImg());holder.tv1.setText(list.get(
Wesley13 Wesley13
3年前
Unity3D中UGUI不使用DOTween制作渐隐渐现效果
在做UI后期设计时,我们可能要对UI做一些特效,这篇文章我们来学习下如何在Unity3d中对实现渐隐渐现的效果,首先我们看下UnityNewUI即UGUI中渐隐渐现的做法.观察我们会发现Unity4.6UI中每个能够显示控件都会有一个CanvasRender对象,CanvasRender有什么作用呢,我们看下官方的解释:TheCanvas
Stella981 Stella981
3年前
Spring 注解事件Event
Stringevent从Spring的4.2版本后,开始支持注解来进行事件广播接收,这使得我们非常方便当然了Spring也支持JMS消息中间件,这个就可以做多个系统集成了,感觉有点偏题了,先看看事件怎么通过注解来开发基础支持先来看看支持哪些默认事件Event描述Context
Easter79 Easter79
3年前
Swing自定义事件
1.Swing自定义事件将一个组件的事件传递给另一个组件.使用EventListenerList来管理事件,当A组件触发事件的时候,调用方法fireActionPerformed()来触发事件,然后再B组件中actionPerformed()方法来接收事件.当在容器KeyTextComponent中按下鼠标,我们就可以在Jframe中捕获触发的事
Wesley13 Wesley13
3年前
unity游戏开发之ULua框架介绍(一)
1.基础介绍①ULua集成开发环境叫做:SimpleFramework,SimpleFramework分为NGUI和UGUI两个版本,区别是NGUI版本的框架资源中含有NGUI这个插件。SimpleFramework本身不是Unitypackage格式,而是一个Unity3D的项目工程,可以用Unity直接打开。②SimpleFram
Wesley13 Wesley13
3年前
Unity 事件系统使用
事件系统上篇简单介绍了一下事件系统,这篇介绍一下怎么使用。UI使用事件系统最常见的使用莫过于我们直接使用的UGUI,当我们创建任意一个UI元素的时候,会自动创建一个Canvas,一个EventSystem。这就启用了unity的事件系统。EventSystem上面挂载有前面我们说的EventSyste
Wesley13 Wesley13
3年前
IOS中键盘自动隐藏
前言很多时候当我们在一个文本框中输入信息后,按了确认或者返回键需要隐藏键盘,或者在其他空白区域点击屏幕后也需要隐藏屏幕。这时肯定就需要让相应的控件响应Tap事件(点击事件),这样我们才能处理。实现隐藏的两种方法在IOS中有一个概念叫FirstResponder,意指第一响应者,也就是当前屏幕上,处于焦点状态的控件,它是第一响
React memo的原理、实践与思考
前言在react中,对一个组件进行点击事件等操作时,该组件以及该组件的子组件都会重新渲染。避免组件的重新渲染一般可以借助React.memo、useCallback等来实现。什么是memomemo原理memo类似于class中pureComponent的特
程序员一鸣 程序员一鸣
5个月前
鸿蒙开发:单一手势实现长按事件
虽然说我们可以通过onTouch来实现一个长按事件,但是如果想要实现连续,多指那么就比较麻烦,远远没有LongPressGesture实现起来简单,所以在实际的开发中,大家还是以LongPressGesture为主。