分析下组件是如何响应事件以及之间的调度结构
下面整理出我的视角分析该事件机制的过程
通过上次的FGUI结构分享所知道 大部分组件都是继承GObject类或者DisplayObject类
GObject 与 DisplayObject 就算是最基本的对象类了
在一个UI系统 里面的子项组件的任务就是承载显示以及触发响应
基础组件需要响应事件
在此引入今天事件机制相关的第一个类EventDispatcher 事件-调度员
GObject/DisplayObject 继承EventDispatcher 为组件提供事件机制
EventDispatcher类是IEventDispatcher接口的实现
IEventDispatcher中约束了 作为事件调度 所须的几个方法
// IEventDispatcher
void AddEventListener(string strType, EventCallback0 callback);
void AddEventListener(string strType, EventCallback1 callback);
void RemoveEventListener(string strType, EventCallback0 callback);
void RemoveEventListener(string strType, EventCallback1 callback);
bool DispatchEvent(EventContext context);
这几个方法名称 添加/移除EventListener()
在参数中见到了 Fgui中定义的两种委托回调
public delegate void EventCallback0();
public delegate void EventCallback1(EventContext context);
这里见到了经常打交道的EventContext 事件-上下文内容 暂时先不展开EventContext
不过看到了EventContext 就会想到平常代码中我们对各种组件事件的设置操作
比如 theBtn.onClick:Add(...)/Set(...)
那来看一下在GObject类中 经常打交道的onClick属性 是什么类型
// GObject
public EventListener onClick
{
get { return _onClick ?? (_onClick = new EventListener(this, "onClick")); }
}
EventListener 事件-监听者 进入我们的视野
// EventListener
EventBridge _bridge;
string _type;
public EventListener(EventDispatcher owner, string type)
{
_bridge = owner.GetEventBridge(type);
_type = type;
}
在EventListener中
存在EventBridge事件-纽带 类型的_bridge对象 以及一个字符串来标识该监听事件类型type
现在继续看常用的Add/Set操作 是怎么做的
// EventListener
public void Add(EventCallback0 callback)
{
_bridge.Add(callback);
}
public void Set(EventCallback1 callback)
{
_bridge.Clear();
if (callback != null)
_bridge.Add(callback);
}
发现其实都是对于_bridge的操作
继续进到EventBridge中 看下有哪些属性和方法
// EventBridge
public EventDispatcher owner;
EventCallback0 _callback0;
EventCallback1 _callback1;
EventCallback1 _captureCallback;
internal bool _dispatching;
public EventBridge(EventDispatcher owner)
{
this.owner = owner;
}
public void Add(EventCallback1 callback)
{
_callback1 -= callback;
_callback1 += callback;
}
public void Remove(EventCallback1 callback)
{
_callback1 -= callback;
}
...
到这里可以看到 我们设置的响应回调 就静静地躺在bridge对象中 等待着被触发响应
看一下响应事件的触发函数
// EventBridge
public void CallInternal(EventContext context)
{
_dispatching = true;
context.sender = owner;
try
{
if (_callback1 != null)
_callback1(context);
if (_callback0 != null)
_callback0();
}
finally
{
_dispatching = false;
}
}
到这里时会想到那这些事件是如何被触发响应的呢
在看如何被触发之前 我们先往回看一下 Listener中bridge的创建过程
// EventListener
public EventListener(EventDispatcher owner, string type)
{
_bridge = owner.GetEventBridge(type);
_type = type;
}
在其构造中调用了调度者的获取方法 此时的调度者其实就是目前设置的组件对象 多态
进到EventDispatcher 中看一下 GetEventBridge
// EventDispatcher
internal EventBridge GetEventBridge(string strType)
{
if (_dic == null)
_dic = new Dictionary();
EventBridge bridge = null;
if (!_dic.TryGetValue(strType, out bridge))
{
bridge = new EventBridge(this);
_dic[strType] = bridge;
}
return bridge;
}
发现每个EventDispatcher都有个string,EventBridge键值对字典
EventDispatcher中的实现 对于之前IEventDispatcher接口中的add remove 操作 也都是对字典的维护
到这里EventListener EventBridge 告一段落
关于触碰判定及响应触发都发生在Stage 中 HandleEvents()
有兴趣的可以去Stage类中看一下
// Stage
void HandleEvents()
{
GetHitTarget();
if (Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.RightShift))
InputEvent.shiftDown = false;
else if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift))
InputEvent.shiftDown = true;
UpdateTouchPosition();
if (_customInput)
{
HandleCustomInput();
_customInput = false;
}
else if (touchScreen)
HandleTouchEvents();
else
HandleMouseEvents();
if (_focused is InputTextField)
HandleTextInput();
}
在HandleEvents时 会根据当前触碰逻辑
去触发GetHitTarget()
时 获取的 TouchInfo中的target的相应事件
之后会调用到焦点组件的BubbleEvent(string strType, object data)
// Stage
DisplayObject clickTarget = touch.ClickTest();
if (clickTarget != null)
{
touch.UpdateEvent();
if (Input.GetMouseButtonUp(1) || Input.GetMouseButtonUp(2))
clickTarget.BubbleEvent("onRightClick", touch.evt);
else
clickTarget.BubbleEvent("onClick", touch.evt);
}
BubbleEvent 是EventDispatcher对象的内部方法 来触发事件
起泡事件 是会对触发组件的父组件 触发同样的操作事件 直到舞台根部
(调用context.StopPropagation()
可以使冒泡停止向父组件推进)
通过 GetChainBridges(strType, bubbleChain, true)
方法
来获取需要触发的bubbleChain(List
// EventDispatcher
internal bool BubbleEvent(string strType, object data, List addChain)
{
EventContext context = EventContext.Get();
context.initiator = this;
context.type = strType;
context.data = data;
if (data is InputEvent)
sCurrentInputEvent = (InputEvent)data;
context.inputEvent = sCurrentInputEvent;
List bubbleChain = context.callChain;
bubbleChain.Clear();
GetChainBridges(strType, bubbleChain, true);
int length = bubbleChain.Count;
for (int i = length - 1; i >= 0; i--)
{
bubbleChain[i].CallCaptureInternal(context);
if (context._touchCapture)
{
context._touchCapture = false;
if (strType == "onTouchBegin")
Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
}
}
if (!context._stopsPropagation)
{
for (int i = 0; i < length; ++i)
{
bubbleChain[i].CallInternal(context);
if (context._touchCapture)
{
context._touchCapture = false;
if (strType == "onTouchBegin")
Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
}
if (context._stopsPropagation)
break;
}
if (addChain != null)
{
length = addChain.Count;
for (int i = 0; i < length; ++i)
{
EventBridge bridge = addChain[i];
if (bubbleChain.IndexOf(bridge) == -1)
{
bridge.CallCaptureInternal(context);
bridge.CallInternal(context);
}
}
}
}
EventContext.Return(context);
context.initiator = null;
context.sender = null;
context.data = null;
return context._defaultPrevented;
}
GetChainBridges
// EventDispatcher
internal void GetChainBridges(string strType, List chain, bool bubble)
{
EventBridge bridge = TryGetEventBridge(strType);
if (bridge != null && !bridge.isEmpty)
chain.Add(bridge);
if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
{
bridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);
if (bridge != null && !bridge.isEmpty)
chain.Add(bridge);
}
if (!bubble)
return;
if (this is DisplayObject)
{
DisplayObject element = (DisplayObject)this;
while ((element = element.parent) != null)
{
bridge = element.TryGetEventBridge(strType);
if (bridge != null && !bridge.isEmpty)
chain.Add(bridge);
if (element.gOwner != null)
{
bridge = element.gOwner.TryGetEventBridge(strType);
if (bridge != null && !bridge.isEmpty)
chain.Add(bridge);
}
}
}
else if (this is GObject)
{
GObject element = (GObject)this;
while ((element = element.parent) != null)
{
bridge = element.TryGetEventBridge(strType);
if (bridge != null && !bridge.isEmpty)
chain.Add(bridge);
}
}
}
在此阶段 我们发现了Context的生命周期:获取、传递与回收
那接下来该看看EventContext 中都有什么
在EventContext之中 存在一个静态栈来作为Context对象池
每次的事件都从池中取出(Get()
)
初始化后 赋予上当前事件属性
响应事件之后又清空 返回池中(Return(EventContext value)
)
EventDispatcher类型的 sender对象
记录了事件触发的分发对象 在EventDispatcher的CallInternal()中赋值
object类型的 initiator对象
记录了事件触发的发起者 在bridge调用CallInternal()之前赋值
一般事件的分发者和发起者是相同的 但如果事件已发生冒泡则可能不相同
string类型的 type对象
记录事件类型 标识
InputEvent类型的 inputEvent对象
如果事件是键盘/触摸/鼠标事件 通过访问inputEvent对象可以获得这类事件的相关数据
object类型的 data对象
事件数据 为用户提供自定义数据传递对象 在bridge调用CallInternal()之前赋值
这是因为对于context的注册方式不同产生的
按钮事件被触发时 此时取得的context.sender
就是该事件的分发对象此按钮自身
因为在 bridge的CallInternal()
时将自身赋值给了sender对象
所以sender的data值即为所要获取按钮的data值
而此时的按钮的data则是 响应事件时传入的touch.evt
clickTarget.BubbleEvent("onClick", touch.evt);
context.sender = owner;
而列表调用onclickItem逻辑 对于子项的处理是不同的
在GList类中发现 在添加列表子项时 会为子项组件默认注册点击事件
//GList
override public GObject AddChildAt(GObject child, int index)
{
base.AddChildAt(child, index);
if (child is GButton)
{
GButton button = (GButton)child;
button.selected = false;
button.changeStateOnClick = false;
}
child.onClick.Add(_itemClickDelegate);
child.onRightClick.Add(_itemClickDelegate);
return child;
}
_itemClickDelegate = __clickItem;
void __clickItem(EventContext context)
{
GObject item = context.sender as GObject;
if ((item is GButton) && selectionMode != ListSelectionMode.None)
SetSelectionOnEvent(item, context.inputEvent);
if (scrollPane != null && scrollItemToViewOnClick)
scrollPane.ScrollToView(item, true);
DispatchItemEvent(item, context);
}
virtual protected void DispatchItemEvent(GObject item, EventContext context)
{
if (context.type == item.onRightClick.type)
DispatchEvent("onRightClickItem", item);
else
DispatchEvent("onClickItem", item);
}
在触发子项点击事件时 会触发内部响应事件 也就是IEventDispatcher接口中的DispatchEvent
因为是逻辑指向 只需要触发其本体 所以不需要BubbleEvent
可以看到此时传进来的data是item
然后对在池中获取到的context.data
赋值
即为传入的item 所以在取data值时 即为context.data.data
// EventDispatcher
public bool DispatchEvent(string strType, object data)
{
return InternalDispatchEvent(strType, null, data, null);
}
public bool DispatchEvent(string strType, object data, object initiator)
{
return InternalDispatchEvent(strType, null, data, initiator);
}
static InputEvent sCurrentInputEvent = new InputEvent();
internal bool InternalDispatchEvent(string strType, EventBridge bridge, object data, object initiator)
{
if (bridge == null)
bridge = TryGetEventBridge(strType);
EventBridge gBridge = null;
if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
gBridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);
bool b1 = bridge != null && !bridge.isEmpty;
bool b2 = gBridge != null && !gBridge.isEmpty;
if (b1 || b2)
{
EventContext context = EventContext.Get();
context.initiator = initiator != null ? initiator : this;
context.type = strType;
context.data = data;
if (data is InputEvent)
sCurrentInputEvent = (InputEvent)data;
context.inputEvent = sCurrentInputEvent;
if (b1)
{
bridge.CallCaptureInternal(context);
bridge.CallInternal(context);
}
if (b2)
{
gBridge.CallCaptureInternal(context);
gBridge.CallInternal(context);
}
EventContext.Return(context);
context.initiator = null;
context.sender = null;
context.data = null;
return context._defaultPrevented;
}
else
return false;
}
EventDispatcher 分发并对事件类型进行管理
EventListener 事件监听对象
EventBridge 事件总线 做回调收集管理与响应
EventContext 事件上下文内容 负责传递信息