Unity UGUI事件转发
在UI界面开发过程中会出现事件接收嵌套的情况。 在大多数情况下这种事件接收嵌套的情况并不会出现问题。 例如点击空白区域的关闭的事件被底板所拦截(需要勾选Raycast Target)。
但是有特殊情况下,需要有条件的转发这些点击、拖动等事件。或者ScrollRect横竖嵌套滑动导致二级ScrollRect拦截到滑动事件而上级的ScrollRect无法进行响应。
源码分析
接收事件
我们知道实现 IPointerEnterHandler
、 IPointerExitHandler
等接口可以获取到对应的事件,而这些接口被定义在EventInterfaces.cs这个文件中。 简略后的代码如下:
// EventInterfaces.cs
namespace UnityEngine.EventSystems {
public interface IEventSystemHandler {}
public interface IPointerEnterHandler : IEventSystemHandler {
void OnPointerEnter(PointerEventData eventData);
}
public interface IPointerExitHandler : IEventSystemHandler {
void OnPointerExit(PointerEventData eventData);
}
public interface IPointerDownHandler : IEventSystemHandler {
void OnPointerDown(PointerEventData eventData);
}
public interface IPointerUpHandler : IEventSystemHandler {
void OnPointerUp(PointerEventData eventData);
}
public interface IPointerClickHandler : IEventSystemHandler {
void OnPointerClick(PointerEventData eventData);
}
public interface IBeginDragHandler : IEventSystemHandler {
void OnBeginDrag(PointerEventData eventData);
}
public interface IInitializePotentialDragHandler : IEventSystemHandler {
void OnInitializePotentialDrag(PointerEventData eventData);
}
public interface IDragHandler : IEventSystemHandler {
void OnDrag(PointerEventData eventData);
}
public interface IEndDragHandler : IEventSystemHandler {
void OnEndDrag(PointerEventData eventData);
}
public interface IDropHandler : IEventSystemHandler {
void OnDrop(PointerEventData eventData);
}
public interface IScrollHandler : IEventSystemHandler {
void OnScroll(PointerEventData eventData);
}
// ...
}
事件发送
接收事件的地方有了,还缺发送事件的地方。 这个发送事件的地方是由 EventSystem
驱动的,由Standalone Input Module
(如果没有变更的话)来进行调用的。具体的判断鼠标移动、点击、放开等条件我们暂且放置一边。简化后的伪代码如下:
// 安装节点递归冒泡的形式获取能够事件的节点。 如果自身没有则查找父节点。能够处理的标志就是找到了实现了对应事件接口的组件
// 这里将查找到的节点放在go变量中,实际情况是放在pointerEvent中对应属性上
GameObject go = ExecuteEvents.GetEventHandler<IEventSystemHandler>(GameObject root);
if (go != null){
// 找到接收对应事件的GameObject,则交由 ExecuteEvents.Execute 执行
ExecuteEvents.Execute(go, pointerEvent, ExecuteEvents.Handler);
}
事件调用执行
// ExecuteEvents.cs
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
{
// 获取 target 上实现了对应事件接口的组件列表
var internalHandlers = s_HandlerListPool.Get();
GetEventList<T>(target, internalHandlers);
// 遍历
for (var i = 0; i < internalHandlers.Count; i++)
{
// 调用执行, 实际调用形如 (T)internalHandlers[i].OnBeginDrag(eventData)
functor((T)internalHandlers[i], eventData);
}
}
解决思路
通过UGUI的源码分析我们可以知道,当事件被处理后不会再对父节点等进行冒泡处理。而调用对应节点处理的关键代码就如下一行:
ExecuteEvents.Execute(go, pointerEvent, ExecuteEvents.Handler);
所以要进行UI事件的转发只要让我们在对应的子节点的对应实现的事件接口中手动调用即可对其事件进行冒泡处理。