• 关于cocostudio动态添加控件触摸响应无效的学习


    time:2015/04/19

    1. 描述

      

      * 把studio制作的ui加载之后,动态添加事件(比如说,单点触摸),结果回调函数(eg:onTouchBegan等)根本没有响应!

      * 另外,网上有朋友也碰到过这个问题,也在网上查到了类似的情况

      * 但是,自己觉得按照3.0的cocos引擎的新渲染逻辑应该是可以设置的,而且这个问题也没有理解清楚,所以自己去看了下源码研究了一下,做一下记录,当然能帮到其他人理解就更好了。

    2. 学习

    (1)直接上代码看结果

    /******说明******/

    * layer: 一个基本的层

    * layout: cocostudio制作的UI

    * 层次关系: layer:addChild(layout)

    * 动态添加一个按钮或者精灵

    /******步骤******/

    * 按钮和layout在一个层级,都加载layer上,没有位置上的重叠 ---> 结果没有影响,两遍都能响应触摸事件

    * 按钮和layout在一个层级,都加载layer上,有位置上的重叠 --->结果依旧没有影响

    * 按钮在layout之前addchild --->直接看不见了

    * 按钮在layout之后addchild --->没有影响

    * 按钮属于layout,layout:addChild(),没有位置上的重叠 --->结果没有影响

    * 按钮属于layout,layout:addChild(),有位置上的重叠 --->结果没有影响

    * 添加一个精灵,自己添加触摸事件 ---> 也没有影响

    Sprite* sprite = Sprite::create("cocosui/green_edit.png");
    bgd->addChild(sprite, 0);
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = CC_CALLBACK_2(UIButtonTest_Editor::onTouchBegan, this);
    listener->onTouchEnded = CC_CALLBACK_2(UIButtonTest_Editor::onTouchEnded, this);
     _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

    (2)那为什么自己会有不响应的问题?

      看自己的代码:

    local function onTouchesBegan(touches,event)
            print("onTouchesBegan ... ")
            return true
        end
    
    
        local function onTouchesMoved(touches, event )
            print("onTouchesMoved ... ")
    
        end
    
        local function onToucheEnd(touches,event)
            print("onToucheEnd ... ")
        end
    
        local listener = cc.EventListenerTouchOneByOne:create()
        listener:registerScriptHandler(onTouchesBegan,cc.Handler.EVENT_TOUCH_BEGAN)
        listener:registerScriptHandler(onTouchesMoved,cc.Handler.EVENT_TOUCH_MOVED)
        listener:registerScriptHandler(onToucheEnd,cc.Handler.EVENT_TOUCH_ENDED)
        local eventDispatcher = self:getEventDispatcher()
       eventDispatcher:addEventListenerWithSceneGraphPriority(listener, self)

    小结:

      *自己是在layer上直接添加了单点触摸的事件

      *用的是addEventListenerWithSceneGraphPriority接口,那么就去看看源码是怎么处理的?

    (3)源代码分析:FixedPriority

      直接看源码,会发现下面这个函数:

    void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID)
    {
        DirtyFlag dirtyFlag = DirtyFlag::NONE;
        
        auto dirtyIter = _priorityDirtyFlagMap.find(listenerID);
        if (dirtyIter != _priorityDirtyFlagMap.end())
        {
            dirtyFlag = dirtyIter->second;
        }
        
        if (dirtyFlag != DirtyFlag::NONE)
        {
            // Clear the dirty flag first, if `rootNode` is nullptr, then set its dirty flag of scene graph priority
            dirtyIter->second = DirtyFlag::NONE;
    
            if ((int)dirtyFlag & (int)DirtyFlag::FIXED_PRIORITY)
            {        //自定义优先级排序
                sortEventListenersOfFixedPriority(listenerID);
            }
            
            if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY)
            {
                auto rootNode = Director::getInstance()->getRunningScene();
                if (rootNode)
                {           //默认优先级为0的排序
                    sortEventListenersOfSceneGraphPriority(listenerID, rootNode);
                }
                else
                {
                    dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY;
                }
            }
        }
    }

    小结1:

    * 可以看到在cocos是有事件优先级的,其中addEventListenerWithFixedPriority可以自定义优先级,addEventListenerWithSceneGraphPriority的优先级默认为0

    * 既然有优先级的区别,那么毋庸置疑,设置一下就可以了

    测试1:

      把自己代码中的最后一行改成下面:

    eventDispatcher:addEventListenerWithFixedPriority(listener, 1)

    结果1:依旧是没有响应
    小结2:

      *继续看代码,发现优先级是按照<0, 0, >0排序的(这里和节点的渲染顺序是一样的)

    // After sort: priority < 0, > 0
        std::sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
            return l1->getFixedPriority() < l2->getFixedPriority();
        });

      * 所以把优先级改成小于0,应该就可以了

    测试2:

    eventDispatcher:addEventListenerWithFixedPriority(listener, -1)

    结果2:
      *果真!有响应了,自己的打印信息出来了,也就是自己的layer在layout之前接收到并且响应了触摸消息

    小结:

      * 事件有优先级区别,但是优先级越低,越先接收并且响应消息!所以,如果是cocostudio的ui接收到了触摸消息,对不起了,后面都不会响应了,因为被吞掉了 [_touchListener->setSwallowTouches(true);]

      * 如果优先级都为0(即,SceneGraphPriority),顺序又是怎么处理的呢?

    (4)源代码:SceneGraphPriority

      首先,看源码的排序地方

    void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener::ListenerID& listenerID, Node* rootNode)
    {
        auto listeners = getListeners(listenerID);
        
        if (listeners == nullptr)
            return;
        auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners();
        
        if (sceneGraphListeners == nullptr)
            return;
    
        // Reset priority index
        _nodePriorityIndex = 0;
        _nodePriorityMap.clear();
    
        visitTarget(rootNode, true);
        
        // After sort: priority < 0, > 0
        std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
            return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
        });
        
        //省略  
    }

      *仔细看visitTarget中的函数,牵涉到了层级关系,zOrder,所以想:设置这个是不是就能达到事件优先级的大小的呢?

    测试1:

        self:setLocalZOrder(-1)
        self:setLocalZOrder(100)
        self:setGlobalZOrder(-1)
        self:setGlobalZOrder(1)        

    结果1:
      * self:setLocalZOrder(-1) ---> 无影响

      * self:setLocalZOrder(100) ---> 无影响

      * self:setGlobalZOrder(-1) ---> 无影响

      * self:setGlobalZOrder(1) ---> 有影响

      * localZorder为什么没有影响

      * 而设置GlobalZOrder是有影响的

    小结1:

      * SceneGraphPriority模式下,是按照>0, 0, <0的顺序去处理事件节点的(注释的地方有问题!!!)

      visitTarget(rootNode, true);
        
        // After sort: priority < 0, > 0
        std::sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
            return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
        });

     

      * 顺序是按照节点的globalZOrder这个值进行排序的,不管是不是child或者parent以及他们之间的所属关系

      * 如果localZOrder的话,都一样的话就按照原来的顺序(即加入节点的顺序);另外父节点和子节点的顺序是:子节点<0的所有点--->父节点--->子节点大于0 的所有点(所以在这里设置layer的localZOrder没有用,要设置layout的localZOrder)

    3. 总结

    (1)FixedPriority设置优先级的话,处理顺序有优先级之分: <0, 0, >0

    (2)SceneGraphPriority模式下,是按照>0, 0, <0的顺序去处理事件节点的

    4. 参考

    [1] http://www.it165.net/pro/html/201405/13283.html

    ---------------------------------后续学习补充--------------------------------------

    1. 关于cocstudio的panel添加触摸事件无效的研究

    时间:2015/05/23

    描述:在cocostudio的界面上添加了一个panel,想对这个panel添加触摸事件,代码如下:

    local listener = cc.EventListenerTouchOneByOne:create();
            listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN)
            listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_ENDED)
            local eventDispatcher = self:getEventDispatcher()
            eventDispatcher:addEventListenerWithFixedPriority(listener, 1)

      参考了之前的研究,这里使用了优先级的监听事件,结果不管是优先级为-1还是1都是不管用的,非常纳闷~

    学习:

    (1)registerScriptHandler

      listener的注册函数,这个是专门为lua写的接口,可以看到这个函数是在lua_cocos2dx_manual.cpp中,并且是把回调函数放在了c++ 11的function类型中做处理的。

                case ScriptHandlerMgr::HandlerType::EVENT_TOUCH_ENDED:
                    {
                        self->onTouchEnded = [=](Touch* touch, Event* event){
                            LuaEventTouchData touchData(touch, event);
                            BasicScriptData data((void*)self,(void*)&touchData);
                            LuaEngine::getInstance()->handleEvent(type, (void*)&data);
                        };
                        
                        ScriptHandlerMgr::getInstance()->addObjectHandler((void*)self, handler, type);
                    }
                    break;
    

      通过调试断点,看了下这个函数的地址(因为一直没有调用进来,所以看看后面是怎么处理的)。

    (2)addEventListenerWithFixedPriority

      通过这个接口加到列表中,没有问题。这里看代码可以看到开始是加到一个临时变量中,最后在updateListeners函数中再把listener加到真正处理的列表中的。

    (3)dispatchTouchEvent

    if (eventCode == EventTouch::EventCode::BEGAN)
                    {
                        if (listener->onTouchBegan)
                        {
                            isClaimed = listener->onTouchBegan(*touchesIter, event);
                            if (isClaimed && listener->_isRegistered)
                            {
                                listener->_claimedTouches.push_back(*touchesIter);
                            }
                        }
                    }

      在这个函数位置进行处理的listener->onTouchBegan处理。

      也就是在这个时候,我开始发现自己一直理解错了。因为panel本身也是接收触摸事件的,看代码可以发现所有的Widget本身就是做了触摸事件处理的!多此一举!!!

    (4)小结

    addEventListenerWithFixedPriority依旧是有效的,设置为-1,就会优先执行,但是一定要注意:这个时候的回调函数的参数和onTouchBegan的回调函数参数是完全不一样的

    // 1. addEventListenerWithFixedPriority的回调函数形式
    bool Widget::onTouchBegan(Touch *touch, Event *unusedEvent)
    
    // 2.addTouchEventListerner的回调函数形式
    typedef std::function<void(Ref*,Widget::TouchEventType)> ccWidgetTouchCallback;

    * 所有的widget自己已经实现了触摸事件,所以不要多此一举!只有自己自定义的类,精灵、层等才需要自己去实现和添加。

    * 除了触摸事件,还有FocusEvent,这个要具体去看看。

    2. cocstudio中控件的响应

    时间:2015/08/27

    描述:cocostudio中控件有的可以点击,有的不可以点击,panel能遮挡,image和lable不遮挡,这些是为什么?

    (1)cocostudio所有的控件都是继承widget,而widget都是实现了单点触摸,参见函数:

    void Widget::setTouchEnabled(bool enable)

    (2)所以,image和label都是可以监听触摸事件的,不过首先要设置一下,所以他们添加的时候要多设置一下上面的函数

    local fnTouchImg = function(sender, eventType)
                if eventType == ccui.TouchEventType.ended then
                    print("press image ... ")
                end
            end
            local img = node:getChildByName("Image_46")
            img:setTouchEnabled(true);
            img:addTouchEventListener(fnTouchImg)

    (3)panel和button默认都是添加了触摸事件,而image和label没有,主要原因是cocostudio上面的【交互】是否勾选,然后在加载json文件的时候调用setTouchEnabled函数

    //WidgetReader.cpp
            widget->setTag(DICTOOL->getIntValue_json(options, P_Tag));
            widget->setActionTag(DICTOOL->getIntValue_json(options, P_ActionTag));
            widget->setTouchEnabled(DICTOOL->getBooleanValue_json(options, P_TouchAble));
            const char* name = DICTOOL->getStringValue_json(options, P_Name);

    (4)总结:

    * cocostudio下面所有控件(继承widget)都是有触摸响应事件的,直接用函数addTouchEventListener即可

    * 对于没有反应的说明setTouchEnabled()是false

    * 对于cocostudio来说,一般都是【交互】没有勾上的原因

  • 相关阅读:
    Jenkins 完成安装环境配置
    Jenkins中文社区的所有镜像地址
    VueX源码分析(3)
    VueX源码分析(2)
    VueX源码分析(1)
    Element表单验证(2)
    Element表单验证(1)
    配置淘宝镜像,不使用怪异的cnpm
    React动态import()
    cnpm 莫名奇妙bug 莫名奇妙的痛
  • 原文地址:https://www.cnblogs.com/pk-run/p/4440494.html
Copyright © 2020-2023  润新知