需求
想要一个工具,能够同时集成单击、双击、长按的功能
思路
通过IPointerDownHandler, IPointerUpHandler, IPointerClickHandler这三个接口就可以监听点击状态,然后再通过不同的点击状态来处理相应的事件
遇到的问题
- 由于可能同时存在多个事件,实际开发过程中会出现多余事件通知,如下:
- 同时拥有单击和双击事件,点击双击的同时会响应两次单击
- 同时拥有单击事件和长按事件时,当长按事件响应的时候会同时响应单击事件
- 同时拥有单击双击和长按事件时点击双击时会同时响应两次单击,长按事件响应时会同时响应单击事件
解决问题
- 由于我们无法预测用户的具体行为,所以当需要响应单击事件时,检测其他事件有没有在安全时效内的检测体,例如,单击响应的时候如果有其他事件正在检测过程中我们忽略这次的事件响应,并把事件存起来,等待所有事件的安全时效结束时响应这次单击事件。或者是等待过程中有其他事件成功响应那么我们就舍弃这次的单击事件而响应对应的双击或者长按事件。那么这样就完美解决了上述所有问题。
具体代码如下:
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;
}
}
使用
public GameObject uiObj;
//点击事件注册
uiObj.OnClick(() =>
{
Debug.Log("OnClick");
});
//双击事件注册
uiObj.OnDoubleClick(() =>
{
Debug.Log("OnDoubleClick");
});
//长按事件注册
uiObj.OnPress(() =>
{
Debug.Log("OnPress");
});