整理FairyGUI的事件机制

整理FairyGUI的事件机制

分析下组件是如何响应事件以及之间的调度结构
下面整理出我的视角分析该事件机制的过程

通过上次的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))


再看一下起到传递作用的上下文对象EventContext中有哪些属性吧

EventDispatcher类型的 sender对象
  记录了事件触发的分发对象 在EventDispatcher的CallInternal()中赋值
object类型的 initiator对象
  记录了事件触发的发起者 在bridge调用CallInternal()之前赋值
  一般事件的分发者和发起者是相同的 但如果事件已发生冒泡则可能不相同
string类型的 type对象
  记录事件类型 标识
InputEvent类型的 inputEvent对象
  如果事件是键盘/触摸/鼠标事件 通过访问inputEvent对象可以获得这类事件的相关数据
object类型的 data对象
  事件数据 为用户提供自定义数据传递对象 在bridge调用CallInternal()之前赋值


在使用中 会有种情况
在获取按钮的data时 使用的是 context.snder.data
而在获取列表项data时 需要使用的是 context.data.data
这是为什么呢

这是因为对于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;
    }

所以总结下来 FGUI的整个事件机制结构

EventDispatcher 分发并对事件类型进行管理
EventListener 事件监听对象
EventBridge 事件总线 做回调收集管理与响应
EventContext 事件上下文内容 负责传递信息

最后附类图一张

整理FairyGUI的事件机制_第1张图片


你可能感兴趣的:(整理FairyGUI的事件机制)