【cocos2d-x 】解决scrollview上的menu拖动问题以及menu item在可视区外仍能触发的问题

【cocos2d-x 】解决scrollview上的menu拖动问题以及menu item在可视区外仍能触发的问题,第1张

概述在使用cocos2d-x的scroll view的时候,会遇到两个问题: 1)scroll view上放menu时,如果拖动menu scroll view不会被拖动,如果scroll view上全是按钮就几乎没地方可以拖动了。 2)当menu item滚动出scrollview的可视区域时,仍然能被触发。 这两个问题确实挺讨厌的,大大影响用户体验,我看到一些基于cocos2d-x的热门游戏也有这 在使用cocos2d-x的scroll vIEw的时候,会遇到两个问题:
1)scroll vIEw上放menu时,如果拖动menu scroll vIEw不会被拖动,如果scroll vIEw上全是按钮就几乎没地方可以拖动了。
2)当menu item滚动出scrollvIEw的可视区域时,仍然能被触发。 这两个问题确实挺讨厌的,大大影响用户体验,我看到一些基于cocos2d-x的热门游戏也有这样的问题,比如某三国的选服界面。

其实也好解决,只需要稍微修改一下Ccmenu的代码即可。
--------------cocos2dx 2.x版本
1)拖动问题:(PS:-号代表引擎的,要注释掉,+号代表新加的)
这是由于默认情况下menu如果处理了事件则会吞掉,这样scroll vIEw就收不到事件了,自然无法拖动。
voID Ccmenu::registerWithtouchdispatcher() {     CCDirector* pDirector = CCDirector::sharedDirector();-    pDirector->gettouchdispatcher()->addTargetedDelegate(this,this->gettouchPriority(),true);+    pDirector->gettouchdispatcher()->addTargetedDelegate(this,m_bSwallowstouches); }

如上面的代码diff,我们只需要设置一个变量m_bSwallowstouches来控制是否吞事件,当然为了兼容,m_bSwallowstouches默认为true
然后我们只要加一个方法 voID setSwallowstouches(bool isSwallowstouches); 来控制这个menu是否吞事件。当menu被放到一个scroll vIEw里面时,只要调用
setSwallowstouches(false);则拖动按钮时scroll vIEw就会被拖动,并且这个不影响按钮本身的事件处理。

但是事情并没有完,可以拖动后引发了一个新问题:当拖动按钮带着scroll vIEw拖动之后,松开手,按钮被触发了,这样感觉不太爽。其实这个也好解决的,只要判断菜单的世界坐标变了即可。
给Ccmenu增加一个成员CCPoint m_touchBeginWorldPos;用来记录touch begin时有menu item被选中时菜单的世界坐标。
Ccmenu::cctouchBegan中修改如下:
if (m_pSelectedItem)    {        m_eState = kCcmenuStateTrackingtouch;        m_pSelectedItem->selected();                if(!m_bSwallowstouches){            m_touchBeginWorldPos = convertToWorldspace(getposition());        }                return true;    }

同样为了效率,这儿要检查一下是否吞事件。
然后在touch end时再获取一下menu的世界坐标,比较一下是否有变化,如果有变化则说明菜单随着scroll vIEw移动了,因此可以取消菜单项的激活。
这是修改后的代码:
voID Ccmenu::cctouchended(CCtouch *touch,CCEvent* event){    CC_UNUSED_ParaM(touch);    CC_UNUSED_ParaM(event);    CCAssert(m_eState == kCcmenuStateTrackingtouch,"[Menu cctouchended] -- invalID state");    if (m_pSelectedItem)    {        m_pSelectedItem->unselected();                do        {            if(!m_bSwallowstouches){                CCPoint newWorldPos = convertToWorldspace(getposition());                const static float kMenuMinMove = 2;                if (fabs(newWorldPos.x - m_touchBeginWorldPos.x)>kMenuMinMove || fabs(newWorldPos.y-m_touchBeginWorldPos.y)>kMenuMinMove) {                    break;                }            }                    m_pSelectedItem->activate();        }        while (false);    }    m_eState = kCcmenuStateWaiting;}

kMenuMinMove是一个冗余量,避免手抖按不上的问题:)

2)menu item可视区域外被触发的问题:
这个问题的原因是menu的事件处理并不知道scroll vIEw的存在,也就更不会知道scroll vIEw绘制时使用了opengl的scissor测试在屏幕上剪切出一个区域,使得只能绘制在该区域中。
因此要解决这个问题,我们只要当menu在scroll vIEw中时,检测touch点是否发生再scissor Box中,如果在scrissor Box之外则返回false。
为了效率优化(使用opengl的API去取状态也是要尽量避免的),我们要加一个开关,只有menu在scroll vIEw中时才打开这个开关。这儿还有一个问题是是否要测试正在进行scissor,其实是没法测试的,因为scroll vIEw在绘制完成之后就会disable scissor。所以我们的修改如下:
首先,Ccmenu.cpp要include一些头文件:

#include "platform/CCEGLVIEwProtocol.h"#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)#include "platform/ios/CCEGLVIEw.h"#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)#include "platform/androID/CCEGLVIEw.h"#endif

因为我们需要使用CCEGLVIEwProtocol的getScissorRect方法,但CCEGLVIEwProtocol只是一个虚基类,我们必须要使用他们的子类来调用getScissorRect方法,因此这儿很麻烦的根据不同的平台include了不同的CCEGLVIEw.h,以后如果要增加平台这儿还要改,确实不太爽,但引擎就是这个结构没办法。
然后修改一下Ccmenu::cctouchBegan:
bool Ccmenu::cctouchBegan(CCtouch* touch,CCEvent* event){    CC_UNUSED_ParaM(event);    if (m_eState != kCcmenuStateWaiting || ! m_bVisible || !m_bEnabled)    {        return false;    }    for (CCNode *c = this->m_pParent; c != NulL; c = c->getParent())    {        if (c->isVisible() == false)        {            return false;        }    }        if (m_bCheckScissor) {        CCEGLVIEwProtocol* eglVIEw = CCEGLVIEw::sharedOpenGLVIEw();        const CCRect & scissorBox = eglVIEw->getScissorRect();        if(! scissorBox.containsPoint(touch->getLocation())){            return false;        }    }    m_pSelectedItem = this->itemFortouch(touch);    if (m_pSelectedItem)    {        m_eState = kCcmenuStateTrackingtouch;        m_pSelectedItem->selected();                if(!m_bSwallowstouches){            m_touchBeginWorldPos = convertToWorldspace(getposition());        }                return true;    }    return false;}

m_bCheckScissor是我们加的开关,因此也要加一个方法去设置: voID setCheckScissor(bool isCheckScissor);
默认m_bCheckScissor为false。只有当menu放到scroll vIEw上时才设置为true。


--------------cocos2dx v3.12版本
1.Menu吞掉事件导致ScrollVIEw等无法响应的问题
Menu里面有一句: @H_502_94@ touchListener->setSwallowtouches(true); 将true修改为false后,完全木有问题。

所以花了几分钟自己写了个继承Menu的类,

修改下方法,然后要使用Menu的地方替换为自己的DBMenu就完美解决问题了。

下面是源码头h文件:

#pragma  once#include cocos2d.hUSING_NS_CC;class DBMenu:public Menu{public:    bool init();     /** initializes a Menu with a NSArray of MenuItem objects */    bool initWithArray(const Vector<menuitem*>& arrayOfItems);    static DBMenu* createWithArray(const Vector<menuitem*>& arrayOfItems);    static DBMenu* createWithItem(MenuItem* item);     /** creates a Menu with MenuItem objects */    static DBMenu* createWithItems(MenuItem *firstItem,va_List args);    static DBMenu* create(MenuItem* item,...) CC_REQUIRES_NulL_TERMINATION;private: };下面是源码cpp文件:#include DBMenu.hbool DBMenu::init(){    return initWithArray(Vector<menuitem*>());} bool DBMenu::initWithArray( const Vector<menuitem*>& arrayOfItems ){    if (Layer::init())    {        _enabled = true;        // menu in the center of the screen        Size s = Director::getInstance()->getWinSize();         this->ignoreAnchorPointForposition(true);        setAnchorPoint(Vec2(0.5f,0.5f));        this->setContentSize(s);         setposition(Vec2(s.wIDth/2,s.height/2));         int z=0;         for (auto& item : arrayOfItems)        {            this->addChild(item,z);            z++;        }         _selectedItem = nullptr;        _state = Menu::State::WAITING;         // enable cascade color and opacity on menus        setCascadecolorEnabled(true);        setCascadeOpacityEnabled(true);          auto touchListener = EventListenertouchOneByOne::create();        touchListener->setSwallowtouches(false);         touchListener->ontouchBegan = CC_CALLBACK_2(DBMenu::ontouchBegan,this);        touchListener->ontouchmoved = CC_CALLBACK_2(DBMenu::ontouchmoved,this);        touchListener->ontouchended = CC_CALLBACK_2(DBMenu::ontouchended,this);        touchListener->ontouchCancelled = CC_CALLBACK_2(DBMenu::ontouchCancelled,this);         _eventdispatcher->addEventListenerWithSceneGraPHPriority(touchListener,this);         return true;    }    return false;} DBMenu* DBMenu::createWithArray( const Vector<menuitem*>& arrayOfItems ){    auto ret = new DBMenu();    if (ret && ret->initWithArray(arrayOfItems))    {        ret->autorelease();    }    else    {        CC_SAFE_DELETE(ret);    }     return ret;} DBMenu* DBMenu::createWithItem( MenuItem* item ){    return DBMenu::create(item,nullptr);} DBMenu* DBMenu::createWithItems( MenuItem *item,va_List args ){    Vector<menuitem*> items;    if( item )    {        items.pushBack(item);        MenuItem *i = va_arg(args,MenuItem*);        while(i)        {            items.pushBack(i);            i = va_arg(args,MenuItem*);        }    }     return DBMenu::createWithArray(items);} DBMenu* DBMenu::create( MenuItem* item,... ) CC_REQUIRES_NulL_TERMINATION{    va_List args;    va_start(args,item);     DBMenu *ret = DBMenu::createWithItems(item,args);     va_end(args);     return ret;}

2)当menu item滚动出scrollvIEw的可视区域时,仍然能被触发。
直接上代码:
//add by hj:startvoID Menu::settouchlimit(cocos2d::Node *node){    m_sztouchlimitNode=node;    m_btouchlimit=true;}bool Menu::isIntouchlimit(touch* touch){    if(m_btouchlimit)    {        Vec2 touchLocation = touch->getLocation();                Vec2 local = m_sztouchlimitNode->convertToNodeSpace(touchLocation);        Rect r = m_sztouchlimitNode->getBoundingBox();        r.origin = Vec2::ZERO;                if (!r.containsPoint(local))        {            return true;        }    }    return false;}//add by hj:end

在ontouchBegan中:
bool Menu::ontouchBegan(touch* touch,Event* event){    auto camera = Camera::getVisitingCamera();    if (_state != Menu::State::WAITING || ! _visible || !_enabled || !camera)    {        return false;    }        for (Node *c = this->_parent; c != nullptr; c = c->getParent())    {        if (c->isVisible() == false)        {            return false;        }    }        //add by hj:start    if(isIntouchlimit(touch))    {        return false;    }    //add by hj:end        _selectedItem = this->getItemFortouch(touch,camera);    if (_selectedItem)    {        _state = Menu::State::TRACKING_touch;        _selecteDWithCamera = camera;        _selectedItem->selected();                return true;    }        return false;}

在使用过程中:

menu2->settouchlimit(tablevIEw);

把当前的tableVIEw 、ScrollVIEw传过去就行了。

总结

以上是内存溢出为你收集整理的【cocos2d-x 】解决scrollview上的menu拖动问题以及menu item在可视区外仍能触发的问题全部内容,希望文章能够帮你解决【cocos2d-x 】解决scrollview上的menu拖动问题以及menu item在可视区外仍能触发的问题所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1081770.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-27
下一篇2022-05-27

发表评论

登录后才能评论

评论列表(0条)

    保存