首先给出 Button 的源码(此处使用的是 Unity 2021 当中的源码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class Button : Selectable, IPointerClickHandler, ISubmitHandler { [Serializable] public class ButtonClickedEvent : UnityEvent {}
[FormerlySerializedAs("onClick")] [SerializeField] private ButtonClickedEvent m_OnClick = new ButtonClickedEvent();
protected Button() { } public ButtonClickedEvent onClick { get { return m_OnClick; } set { m_OnClick = value; } }
private void Press() { if (!IsActive() || !IsInteractable()) return; UISystemProfilerApi.AddMarker("Button.onClick", this); m_OnClick.Invoke(); } public virtual void OnPointerClick(PointerEventData eventData) { if (eventData.button != PointerEventData.InputButton.Left) return; Press(); } public virtual void OnSubmit(BaseEventData eventData) { Press(); if (!IsActive() || !IsInteractable()) return; DoStateTransition(SelectionState.Pressed, false); StartCoroutine(OnFinishSubmit()); }
private IEnumerator OnFinishSubmit() { var fadeTime = colors.fadeDuration; var elapsedTime = 0f; while (elapsedTime < fadeTime) { elapsedTime += Time.unscaledDeltaTime; yield return null; } DoStateTransition(currentSelectionState, false); } }
|
大体逻辑如代码注释所示。此处 Button 实现了 IPointerClickHandler
接口的 OnPointerClick
方法。那么此处我们就去找寻一下调用链,观察这个方法到底是由谁来调用的。
调用链
查看引用可知,是 ExecuteEvents
类的 Excute
方法(此方法有多个重载,提供了许多通用的事件处理方法)来调用的。
1 2 3 4 5
| private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute; private static void Execute(IPointerClickHandler handler, BaseEventData eventData) { handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData)); }
|
大体的类关系如图所示,ExcuteEvents
当中的 Excute
方法会在鼠标松开时通过 Excute
(和前者不是一个方法)去调用 OnPointerClick
和 OnSubmit
方法。因而就触发了 Button 的 onClick
事件。
在 ExcuteEvents
当中,还定义了一个 EventFunction<T1>
的泛型委托,用于定义通用的事件处理方法,接着还定义了一个 pointerClickHandler
的属性,返回 s_PointerClickHandler
字段,也就是我们上面定义的存储处理点击的 Excute
方法的委托。
1 2 3 4 5
| public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData); public static EventFunction<IPointerClickHandler> pointerClickHandler { get { return s_PointerClickHandler; } }
|
接着我们该查看这个 pointerClickHandler
是由谁来调用的:
StandaloneInputModule.ReleaseMouse
StandaloneInputModule.ProcessTouchPress
TouchInputModule.ProcessTouchPress
此处的 StandaloneInputModule
和 TouchInputModule
都是继承自 BaseInputModule
。其主要是用于处理鼠标,键盘,控制器等设备的输入。
此时我们可以自顶向下来看:EventSystem
类,也就是我们新建 UI 会自动生成的那个组件。其的 Update
方法中会每帧更新检查可用的输入模块的状态是否变化。也就是其中的 ChangeEventModule
。并且会每帧调用 TickModules
(用来更新每个模块的状态),最后会调用当前每个模块( m_CurrentInputModule
)的 Process
方法。
在 TouchInputModule
当中:
入口 Process()
,调用 ProcessTouchEvents()
,接着调用 ProcessTouchPress()
去调用到了 pointerClickHandler
。
1 2 3 4 5 6 7 8 9 10
| / protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released) { if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler); } }
|
在 StandaloneInputModule
当中:’
入口 Process()
,调用 ProcessTouchEvents()
,接着调用 ProcessTouchPress()
,其中的函数签名和调用逻辑也是和上面的一模一样。
1 2 3 4 5 6 7 8 9
| protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released) { if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler); } }
|
同时在 ProcessTouchEvents()
也会调用 ProcessMouseEvent()
,接着调用其自身重载然后调用 ProcessMousePress()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| protected void ProcessMousePress(MouseButtonEventData data) { if (data.ReleasedThisFrame()) { ReleaseMouse(pointerEvent, currentOverGo); } }
private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo) { if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler); } }
|
此时所有的线索都指向了 Excute
的一个版本的重载,我们直接看看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler { var internalHandlers = ListPool<IEventSystemHandler>.Get(); GetEventList<T>(target, internalHandlers); var internalHandlersCount = internalHandlers.Count; for (var i = 0; i < internalHandlersCount; i++) { T arg; try { arg = (T)internalHandlers[i]; } catch (Exception e) { var temp = internalHandlers[i]; Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e)); continue; } try { functor(arg, eventData); } catch (Exception e) { Debug.LogException(e); } }
var handlerCount = internalHandlers.Count; ListPool<IEventSystemHandler>.Release(internalHandlers); return handlerCount > 0; }
|
总结一下:EventSystem
会在 Update()
当中每帧调用当前可用的 BaseInputModule
的 Process()
方法。其主要是用来处理鼠标的按下,抬起等事件。比如当鼠标抬起会调用 ReleaseMouse()
方法,并最终调用 Excute()
方法触发 IPointerClick
接口的 OnPointerClick()
方法。
小问题:ReleaseMouse()
只有抬起鼠标左键才会触发吗?
如果单纯说触发,左键,右键以及滚轮都可以触发 ReleaseMouse()
方法,但是在 Button
的 OnPointerClick()
实现当中,筛掉了非鼠标左键,因此只有鼠标左键才能触发 Button
的点击事件。
1 2 3 4 5 6 7
| public virtual void OnPointerClick(PointerEventData eventData) { if (eventData.button != PointerEventData.InputButton.Left) return; Press(); }
|
以上就是对于 Button 源码的简单拆解。
参考: