码迷,mamicode.com
首页 > 其他好文 > 详细

事件Event

时间:2014-11-19 18:26:02      阅读:262      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   io   ar   os   使用   sp   

EventListener是根据事件来执行一定的动作,前面讲的调度Scheduler根据时间来执行动作

我们先从Event类开始。

打开CCEvent.h文件

bubuko.com,布布扣
/**
 *   Base class of all kinds of events.
 */
class Event : public Ref
{
public:
    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        CUSTOM
    };
    
protected:
    /** Constructor */
    Event(Type type);
public:
    /** Destructor */
    virtual ~Event();

    /** Gets the event type */
    inline Type getType() const { return _type; };
    
    /** Stops propagation for current event */
    inline void stopPropagation() { _isStopped = true; };
    
    /** Checks whether the event has been stopped */
    inline bool isStopped() const { return _isStopped; };
    
    /** @brief Gets current target of the event
     *  @return The target with which the event associates.
     *  @note It onlys be available when the event listener is associated with node. 
     *        It returns 0 when the listener is associated with fixed priority.
     */
    inline Node* getCurrentTarget() { return _currentTarget; };
    
protected:
    /** Sets current target */
    inline void setCurrentTarget(Node* target) { _currentTarget = target; };
    
    Type _type;     ///< Event type
    
    bool _isStopped;       ///< whether the event has been stopped.
    Node* _currentTarget;  ///< Current target
    
    friend class EventDispatcher;
};
bubuko.com,布布扣

这个类并且不复杂,先看一下类的注释,Event类是所有事件类的基类。

类定义的最上面有一个枚举,定义了事件的类型

bubuko.com,布布扣
enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        CUSTOM
    };
bubuko.com,布布扣

事件的各类分别为 ,触摸事件, 键盘事件, 加速器事件,鼠标事件, 用户自定义事件。

再看一下Event类的成员变量

_type 描述当前对象的事件类型。

_isStopped 描述当前事件是否已经停止

_currentTarget 是侦听事件的Node类型的对象

 

 

下面我们再看一下用户自定义事件 EventCustom  类的定义,来了解一下。

bubuko.com,布布扣
class EventCustom : public Event
{
public:
    /** Constructor */
    EventCustom(const std::string& eventName);
    
    /** Sets user data */
    inline void setUserData(void* data) { _userData = data; };
    
    /** Gets user data */
    inline void* getUserData() const { return _userData; };
    
    /** Gets event name */
    inline const std::string& getEventName() const { return _eventName; };
protected:
    void* _userData;       ///< User data
    std::string _eventName;
};
bubuko.com,布布扣

 

在自定义事件类中,多出了两个成员变量 一个是 _userData用来记录用户自定义数据,另一个_eventName用户给这个事件起的别名。

 

下面我们来分析 EventListener 这个类。

这个类看着挺长,其实没什么内容,先看下类的定义。

EventListener也同样定义了一个类型

bubuko.com,布布扣
enum class Type
    {
        UNKNOWN,
        TOUCH_ONE_BY_ONE,
        TOUCH_ALL_AT_ONCE,
        KEYBOARD,
        MOUSE,
        ACCELERATION,
        CUSTOM
    };
bubuko.com,布布扣

这个类型与Event的类型有一小点不同,就是将触摸事件类型分成了 One by One (一个接一个) 与  All At Once (同时一起)两种。

再看 EventListener 的属性

bubuko.com,布布扣
std::function<void(Event*)> _onEvent;   // 用来记录侦听器回调函数

    Type _type;                             // 侦听器的类型
    ListenerID _listenerID;                 // 侦听器的ID 其实是个字符串
    bool _isRegistered;                     // 标记当前侦听器是否已经加入到了事件分发器中的状态变量

    int   _fixedPriority;   // 侦听器的优先级别,数值越高级别越高.默认为0
    Node* _node;            // 场景结点(这里这个变量的作用还没能理解好,后面我们再进行分析)
    bool _paused;           // 标记此侦听器是否为暂停状态。
    bool _isEnabled;        // 标记此侦听器是否有效
bubuko.com,布布扣

结点node是和侦听器EventListener一一对应的关系,当然一个NODE可以对应多个EventListener,EventListener只有一个NODE

在EventListener定义中有两个纯虚函数,我们看一下。

/** Checks whether the listener is available. */
    virtual bool checkAvailable() = 0;

    /** Clones the listener, its subclasses have to override this method. */
    virtual EventListener* clone() = 0;

通过注释了解这两个函数 一个是验证listener是否有效 别一个是clone方法。

EventListener是抽象类,那么咱们找一个它的子类的具体实现。

我们看一下EventListenerCustom这个类的定义。

bubuko.com,布布扣
class EventListenerCustom : public EventListener
{
public:
    /** Creates an event listener with type and callback.
     *  @param eventType The type of the event.
     *  @param callback The callback function when the specified event was emitted.
     */
    static EventListenerCustom* create(const std::string& eventName, const std::function<void(EventCustom*)>& callback);
    
    /// Overrides
    virtual bool checkAvailable() override;
    virtual EventListenerCustom* clone() override;
    
CC_CONSTRUCTOR_ACCESS:
    /** Constructor */
    EventListenerCustom();
    
    /** Initializes event with type and callback function */
    bool init(const ListenerID& listenerId, const std::function<void(EventCustom*)>& callback);
    
protected:
    std::function<void(EventCustom*)> _onCustomEvent;
    
    friend class LuaEventListenerCustom;
};
bubuko.com,布布扣

EventListenerCustom又增加了一个成员变量,_onCustomEvent 接收一个EventCustom类型的事件为参数的回调函数。

我们可以看到,这个类的构造函数不是public形式的,所以这个类不能直接被实例化,

找到了EventListenerCustom提供了一个静态函数 create 所以实例化这个在的对象一定要使用这个create方法。

我们看一下create方法。

bubuko.com,布布扣
EventListenerCustom* EventListenerCustom::create(const std::string& eventName, const std::function<void(EventCustom*)>& callback)
{
    EventListenerCustom* ret = new EventListenerCustom();
    if (ret && ret->init(eventName, callback))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}
bubuko.com,布布扣

这个Create函数的结构,与Node的Create结构一样,新创建的EventListener加入到了autorelease列表里面,在Create的时候调用了init函数,我们再看一下EventListenerCustom::init方法。

bubuko.com,布布扣
bool EventListenerCustom::init(const ListenerID& listenerId, const std::function<void(EventCustom*)>& callback)
{
    bool ret = false;
    
    _onCustomEvent = callback;
    
    auto listener = [this](Event* event){
        if (_onCustomEvent != nullptr)
        {
            _onCustomEvent(static_cast<EventCustom*>(event));
        }
    };
    
    if (EventListener::init(EventListener::Type::CUSTOM, listenerId, listener))
    {
        ret = true;
    }
    return ret;
}
bubuko.com,布布扣

这个函数也没什么特别的,但值得注意的是,CustomEvent的回调与基类的回调函数是怎么关联的。

在EventListenerCustom类中有一个成员变量_onCustomEvent它是一个函数指针来记录事件触发后的回调函数。

在EventListener类中也有一个_onEvent成员变量来记录事件触发的时候的回调函数。

EventListenerCustom::init函数中 有一个匿名函数,listener 在这个匿名函数中 判断了_onCustomEvent是否为空,如果不为空那么调用_onCustomEvent。

后面调用基类的init方法,把这个匿名函数传递给了基类的_onEvent,这样基类的回调函数_onEvent与子类的_onCustomEvent就关联起来了。

下面我们看一下抽象方法在子类中怎么实现的

bubuko.com,布布扣
EventListenerCustom* EventListenerCustom::clone()
{
    EventListenerCustom* ret = new EventListenerCustom();
    if (ret && ret->init(_listenerID, _onCustomEvent))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}
bubuko.com,布布扣

clone的操作与Create方法很象,其实就是重新创建了一个对象,它的_listenerID 、 _onCustomEvent值与原对象一样。返回的是新创建的对象指针。

事件类Event有了,侦听器类EventListener 有了,那么下面我们来分析事件的分发器EventDispatcher。

打开CCEventDispatcher.h文件

bubuko.com,布布扣
/**
This class manages event listener subscriptions
and event dispatching.

The EventListener list is managed in such a way that
event listeners can be added and removed even
from within an EventListener, while events are being
dispatched.
*/
class EventDispatcher : public Ref
{
bubuko.com,布布扣

这个类也是Ref的子类,从注释上面我们先整体的了解EventDispatcher的功能及作用。这个类管理事件侦听脚本及事件的分发处理。有一个事件侦听器列表,来记录所有侦听的事件。分析这个类我们首先还是从这个类的属性上开始。

 

从EventDispatcher的成员变量上看,都是围绕着侦听器列表来定义的,那么我们就看一下把侦听器加入到侦听器列表的方法。

第一个add方法

bubuko.com,布布扣
/** Adds a event listener for a specified event with the priority of scene graph.
     *  @param listener The listener of a specified event.
     *  @param node The priority of the listener is based on the draw order of this node.
     *  @note  The priority of scene graph will be fixed value 0. So the order of listener item
     *          in the vector will be ‘ <0, scene graph (0 priority), >0‘.
     */
    void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);
bubuko.com,布布扣

从注释上可以知道这个方法的作用是,将一个指定的事件侦听器依照场景图的优先级顺序加入到侦听器列表里面, 这个方法与场景图的绘制顺序有关系,

场景的结点渲染顺序也就是zOrder的顺序,场景中的结点优先级一般都是0,侦听器的存放顺序就是 小于0  等于0 大于0这样一个顺序 。

我们看一下这个方法的实现

bubuko.com,布布扣
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
    CCASSERT(listener && node, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    
    if (!listener->checkAvailable())
        return;
    
    listener->setAssociatedNode(node);
    listener->setFixedPriority(0);
    listener->setRegistered(true);
    
    addEventListener(listener);
}
bubuko.com,布布扣

这个方法内容也简单,

1. 先检查了侦听器是否有效

2. 将结点与侦听器做了关联

3. 设置优先级为0,从注释上我们已经得到这个信息了,这个方法加入的侦听器都是显示对象的,所以优先级都为0

4. 设置侦听器已经注册状态

5. 调用了addEventListener方法,将侦听器加入到EventDispatcher的侦听器列表里。

下面我们看一下addEventListener方法,了解是将侦听器加入到侦听器管理列表里的过程

bubuko.com,布布扣
void EventDispatcher::addEventListener(EventListener* listener)
{
    if (_inDispatch == 0)
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);
    }

    listener->retain();
}
bubuko.com,布布扣

这个方法判断了当前 是否在分发消息,如果没有分发消息那么就调用  forceAddEventListener 把侦听器加入到侦听器列表里面。

如果_indispatch不为0证明现在正在分发消息那么新加入的侦听器就放到了临时数组_toAddedListeners里面

不管管理器是不是在分发消息listener都有一个归宿,那么最后增加了listener一次引用计数。

下面我们看一下forceAddEventListener方法。

bubuko.com,布布扣
void EventDispatcher::forceAddEventListener(EventListener* listener)
{
    EventListenerVector* listeners = nullptr;
    EventListener::ListenerID listenerID = listener->getListenerID();
    auto itr = _listenerMap.find(listenerID);
    if (itr == _listenerMap.end())
    {
        
        listeners = new EventListenerVector();
        _listenerMap.insert(std::make_pair(listenerID, listeners));
    }
    else
    {
        listeners = itr->second;
    }
    
    listeners->push_back(listener);
    
    if (listener->getFixedPriority() == 0)
    {
        setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
        
        auto node = listener->getAssociatedNode();
        CCASSERT(node != nullptr, "Invalid scene graph priority!");
        
        associateNodeAndEventListener(node, listener);
        
        if (node->isRunning())
        {
            resumeEventListenersForTarget(node);
        }
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}
bubuko.com,布布扣

这个类里面涉及到了一个EventDispatcher的内部类 EventListenerVector 这样一个数据结构,

这个结构在这里不多分析了,很简单,这个结构里封装了两个数组,_fixedListeners 与_sceneGraphListeners ,分别保存优先级不为0的侦听器指针与优先级为0的侦听器指针。

我们看一下强制将一个侦听器加入到管理列表的过程

  1. _listenerMap是按照侦听器ID来做分类的,每个侦听器ID都有一个EventListenerVector 数组。在_listenerMap中找 listenerID与要加入的listener相同的侦听器列表
  2. 如果没找到就他那天个listenerID为listener->getListenerID();项加入到_listenerMap中。找到了就拿到这个ID的列表指针。
  3. 将要加入管理的侦听器放到列表中。
  4. 根据加入的侦听器的优先级别是不是0进行设置脏标记操作。
  5. 当优先级标记为0时肯定这个侦听器是与场景显示对象对象绑定的,找到这个绑定的Node对象与listener做了关联,调用了associateNodeAndEventListener方法,将结点与侦听器加入到了_nodeListenersMap列表里面。
  6. 因为侦听器有了增加,所以原侦听器列表就不是最新的了,cocos2d-x认为那就是脏数据,这样设置了关于这个侦听器ID的脏标记。

通过上述分析,我们可以进一步理解到EventDispatcher类内的几个侦听器列表变量的作用。

_listenerMap 用以侦听器类型(就是侦听器的ID)索引,值是一个数组,用来储存侦听同一侦听器ID的所有侦听器对象。

_priorityDirtyFlagMap 用来标记一类ID的侦听器列表是对象是否有变化,侦是侦听器ID,值为侦听级别。

_nodeListenersMap 用来记录结点类型数据的侦听器列表,通俗点说就是以结点为索引所有侦听的事件都存在这个map里面。

我们注意这里判断了node->isRunning()属性如果结点是在运行的结点,那么调用了resumeEventListenersForTarget方法。下面看下这个方法都做了些什么。

bubuko.com,布布扣
void EventDispatcher::resumeEventListenersForTarget(Node* target, bool recursive/* = false */)
{
    auto listenerIter = _nodeListenersMap.find(target);
    if (listenerIter != _nodeListenersMap.end())
    {
        auto listeners = listenerIter->second;
        for (auto& l : *listeners)
        {
            l->setPaused(false);
        }
    }
    setDirtyForNode(target);
    
    if (recursive)
    {
        const auto& children = target->getChildren();
        for (const auto& child : children)
        {
            resumeEventListenersForTarget(child, true);
        }
    }
}
bubuko.com,布布扣

这个函数两个参数,第一个是目标结点对象,第二个参数是是否递归进行子对象调用。

这个函数过程,先在结点列表中找是否已经有这个结点了,找到之后将它的每个侦听器的暂停状态都 取消。然后设置这个结点为脏结点标记。

上面提到过设置脏的侦听器,这里看一下设置脏结点函数。

bubuko.com,布布扣
void EventDispatcher::setDirtyForNode(Node* node)
{
    // Mark the node dirty only when there is an eventlistener associated with it. 
    if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
    {
        _dirtyNodes.insert(node);
    }

    // Also set the dirty flag for node‘s children
    const auto& children = node->getChildren();
    for (const auto& child : children)
    {
        setDirtyForNode(child);
    }
}
bubuko.com,布布扣

这个函数虽然没有递归参数来控制,但从实现 上来分析这经会递归 node结点的子结点,都设置成了脏结点。

这里出现了_dirtyNodes这个类成员变量,现在可以理解什么是脏结点了,就是侦听器有变化的结点。

 

下面看一下用户息定义的消息是怎么分发的

dispatchCustomEvent

void EventDispatcher::dispatchCustomEvent(const std::string &eventName, void *optionalUserData)
{
    EventCustom ev(eventName);
    ev.setUserData(optionalUserData);
    dispatchEvent(&ev);
}

参数为一个事件名称和一个用户自定义的数据指针,这里面创建了个文化EventCustom对象,然后调用了dispatchEvent(&ev);之后分发的过程与上面的一样了。

好啊,今天又啰嗦这么多,主要分析了三个东东,Cocos2d-x中的  事件、 侦听器、事件分发器

有经验的同学可以看出,其实这里用到了一个常用的设计模式就是观察者模式,采用了注册事件,触发采用回调函数来执行事件过程。

小鱼在这里总结一下:

  1. Cocos2d-x的事件有几种类型,触摸(TOUCH)、键盘(KEYBOARD)、重力器(ACCELERATION)、鼠标(MOUSE)、用户自定义类型(CUSTOM)
  2. 侦听器也有几种 TOUCH_ONE_BY_ONE, TOUCH_ALL_AT_ONCE, KEYBOARD, MOUSE, ACCELERATION, CUSTOM
  3. 分发器EventDispatcher将事件对象传递给在分发器里注册的事件侦听对象EventListeners,根据事件类型做匹配,匹配到合适的侦听器后就算事件触发了,调用 侦听器的回调函数来执行事件过程。
  4. Cocos2d-x引擎中有一个分发器对象,就是在Direct类中的_eventDispatcher这个变量,在创建Direct对象时进行的初始化。

 

事件Event

标签:des   style   blog   http   io   ar   os   使用   sp   

原文地址:http://www.cnblogs.com/notlate/p/4108537.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!