Unity click, double-click, long press event handling

need

I want a tool that can integrate the functions of single click, double click and long press at the same time

Ideas

Through the three interfaces IPointerDownHandler, IPointerUpHandler, and IPointerClickHandler, you can monitor the click status, and then handle the corresponding events through different click statuses.

Problems encountered

  • Since multiple events may exist at the same time, redundant event notifications will appear during the actual development process, as follows:
    • It has both click and double-click events. Clicking double-click will respond to two clicks at the same time.
    • When there are both click events and long press events, when the long press event responds, the click event will be responded to at the same time.
    • When you have both single-click, double-click and long-press events, the double-click event will respond to two clicks at the same time, and the long-press event will respond to the click event at the same time.

Solve the problem

  • Since we cannot predict the specific behavior of the user, when we need to respond to a click event, we need to detect whether other events have detection objects within the safe time limit. For example, if there are other events being detected when responding to a click, we ignore this time. Respond to the event, save the event, and wait for the click event to be responded to when the security expiration of all events has expired. Or if other events are successfully responded to during the waiting process, then we will abandon this click event and respond to the corresponding double-click or long-press event. Then this perfectly solves all the above problems.

The specific code is as follows:

ClickEvent

public class ClickEvent : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerClickHandler
    {
        private List<IClickListener> listenerList = new List<IClickListener>();
        private Dictionary<IClickListener, Action> callBackDict = new Dictionary<IClickListener, Action>();

        public void OnPointerDown(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerDown(eventData);
            }
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerUp(eventData);
            }
        }

        public void OnPointerClick(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerClick(eventData);
            }
        }

        private void Update()
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.Update();
            }
        }

        public T Add<T>() where T : ClickListener, new()
        {
            T t = Get<T>();
            if (t == null)
            {
                t = new T();
                (t as IClickListener).OnCallBack((success) => OnReceive(success, t));
                if (listenerList.Count == 0 || t is ClickButton) listenerList.Add(t);
                else listenerList.Insert(listenerList.Count - 1, t);
            }

            return t;
        }

        public void Remove<T>() where T : ClickListener
        {
            if (!Contains<T>()) return;
            T t = Get<T>();
            if (callBackDict.ContainsKey(t)) callBackDict.Remove(t);
            listenerList.Remove(t);
        }

        public void RemoveAll()
        {
            listenerList.Clear();
            callBackDict.Clear();
        }

        public T Get<T>() where T : ClickListener
        {
            if (listenerList.Count == 0) return default;
            IClickListener listener = default;
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listener = listenerList[i];
                if (listener is T) return (T) listener;
            }

            return default;
        }

        public bool Contains<T>() where T : ClickListener
        {
            if (listenerList.Count == 0) return false;
            IClickListener listener = default;
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listener = listenerList[i];
                if (listener is T) return true;
            }

            return false;
        }

        public bool ContainsCallBack<T>() where T : ClickListener
        {
            T t = Get<T>();
            if (t == null) return false;
            if (callBackDict.ContainsKey(t))
            {
                if (callBackDict[t] == null)
                {
                    callBackDict.Remove(t);
                    return false;
                }

                return true;
            }
            else return false;
        }

        public Action GetCallBack<T>() where T : ClickListener
        {
            T t = Get<T>();
            if (t == null) return null;
            if (callBackDict.ContainsKey(t))
            {
                Action callBack = callBackDict[t];
                if (callBack == null)
                {
                    callBackDict.Remove(t);
                    return null;
                }

                return callBack;
            }
            else return null;
        }

        public bool HasRuning()
        {
            bool isRuning = false;
            foreach (var lis in listenerList)
            {
                if (lis.isRuning)
                {
                    isRuning = true;
                    break;
                }
            }

            return isRuning;
        }

        public T OnRegister<T>(Action callBack) where T : ClickListener, new()
        {
            T t = Add<T>();
            if (callBackDict.ContainsKey(t)) callBackDict[t] = callBack;
            else callBackDict.Add(t, callBack);
            return t;
        }

        private Action clickCallBack = null;

        void OnReceive(bool success, IClickListener listener)
        {
            if (success)
            {
                if (listener is ClickButton) //单击事件
                {
                    Action cbk = GetCallBack<ClickButton>();
                    if (cbk == null) return;
                    if (HasRuning()) //有正在运行的事件检测单击事件滞后分发
                    {
                        clickCallBack = cbk;
                    }
                    else
                    {
                        clickCallBack = null;
                        ResetOther();
                        cbk.Invoke();
                    }
                }
                else //其他事件
                {
                    clickCallBack = null;
                    ResetOther();
                    if (callBackDict.ContainsKey(listener)) callBackDict[listener]?.Invoke();
                }
            }
            else
            {
                if (!HasRuning())
                {
                    clickCallBack?.Invoke();
                    clickCallBack = null;
                }
            }

            void ResetOther()
            {
                foreach (var btn in listenerList)
                {
                    if (btn != listener) btn?.Reset();
                }
            }
        }

#if UNITY_EDITOR

        [UnityEditor.CustomEditor(typeof(ClickEvent))]
        class ClickEventInspector : UnityEditor.Editor
        {
            private ClickEvent clickEvent;

            private void OnEnable()
            {
                clickEvent = target as ClickEvent;
            }

            public override void OnInspectorGUI()
            {
                base.OnInspectorGUI();
                GUILayout.Label("ClickListeners: ");
                IClickListener listener;
                for (int i = clickEvent.listenerList.Count - 1; i >= 0; i--)
                {
                    listener = clickEvent.listenerList[i];
                    GUILayout.Label("    " + listener);
                }
            }
        }

#endif

        private interface IClickListener
        {
            bool isRuning { get; set; }
            void OnPointerDown(PointerEventData eventData);
            void OnPointerUp(PointerEventData eventData);

            void OnPointerClick(PointerEventData eventData);

            void Update();

            void OnCallBack(Action<bool> callBack);

            void Reset();

            void Dispose();
        }

        public abstract class ClickListener : IClickListener
        {
            bool IClickListener.isRuning
            {
                get => isRuning;
                set { isRuning = value; }
            }

            protected bool isRuning = false;

            protected Action<bool> callBack { get; private set; }
            void IClickListener.OnPointerDown(PointerEventData eventData) => OnPointerDown(eventData);
            void IClickListener.OnPointerUp(PointerEventData eventData) => OnPointerUp(eventData);
            void IClickListener.OnPointerClick(PointerEventData eventData) => OnPointerClick(eventData);
            void IClickListener.Update() => Update();
            void IClickListener.Reset() => Reset();
            void IClickListener.Dispose() => Dispose();

            void IClickListener.OnCallBack(Action<bool> callBack)
            {
                this.callBack = callBack;
            }

            protected virtual void OnPointerDown(PointerEventData eventData)
            {
            }

            protected virtual void OnPointerUp(PointerEventData eventData)
            {
            }

            protected virtual void OnPointerClick(PointerEventData eventData)
            {
            }

            protected virtual void Update()
            {
            }

            protected abstract void Reset();

            protected virtual void Dispose()
            {
                callBack = null;
            }
        }
    }

Buttons

 //单击按钮
    public class ClickButton : ClickEvent.ClickListener
    {
        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
        }

        protected override void OnPointerUp(PointerEventData eventData)
        {
            if (isRuning)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
        }
    }

    //双击按钮
    public class DoubleClickButton : ClickEvent.ClickListener
    {
        public int maxSpaceTime = 250;
        private int clickCount = 0;
        private Stopwatch stopWatch;
        private float lastPointTime = 0;

        public DoubleClickButton()
        {
            stopWatch = new Stopwatch();
            Reset();
        }

        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
        }

        protected override void OnPointerClick(PointerEventData eventData)
        {
            stopWatch.Start();
            clickCount++;
            if (clickCount == 2)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Update()
        {
            if (!stopWatch.IsRunning) return;
            if (stopWatch.ElapsedMilliseconds > maxSpaceTime)
            {
                Reset();
                callBack?.Invoke(false);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
            if (stopWatch.IsRunning) stopWatch.Stop();
            stopWatch.Reset();
            clickCount = 0;
        }
    }

    //长按按钮
    public class PressButton : ClickEvent.ClickListener
    {
        public int pressMinTime = 2000;
        private Stopwatch stopWatch;

        public PressButton()
        {
            stopWatch = new Stopwatch();
        }

        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
            stopWatch.Start();
        }

        protected override void OnPointerUp(PointerEventData eventData)
        {
            Reset();
            callBack?.Invoke(false);
        }


        protected override void Update()
        {
            if (!stopWatch.IsRunning) return;
            if (stopWatch.ElapsedMilliseconds > pressMinTime)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
            if (stopWatch.IsRunning) stopWatch.Stop();
            stopWatch.Reset();
        }
    }

GameObjectExpand

public static class GameObjectExpand
    {
        public static ClickButton OnClick(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            ClickButton button = clickEvent.OnRegister<ClickButton>(callBack);
            return button;
        }

        public static DoubleClickButton OnDoubleClick(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            DoubleClickButton button = clickEvent.OnRegister<DoubleClickButton>(callBack);
            return button;
        }

        public static PressButton OnPress(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            PressButton button = clickEvent.OnRegister<PressButton>(callBack);
            return button;
        }

        static ClickEvent GetClickEvent(GameObject obj)
        {
            ClickEvent clickEvent = obj.GetComponent<ClickEvent>();
            if (clickEvent == null) clickEvent = obj.AddComponent<ClickEvent>();
            return clickEvent;
        }
    }

use

public GameObject uiObj;
//点击事件注册
uiObj.OnClick(() =>
{
    Debug.Log("OnClick");
});
//双击事件注册
uiObj.OnDoubleClick(() =>
{
    Debug.Log("OnDoubleClick");
});
//长按事件注册
uiObj.OnPress(() =>
{
    Debug.Log("OnPress");
});

Guess you like

Origin blog.csdn.net/weixin_42498461/article/details/127675691