【cocos2d-x 】解决scrollview上的menu拖动问题以及menu item在
发布时间:2020-12-14 17:12:11 所属栏目:百科 来源:网络整理
导读:在使用cocos2d-x的scroll view的时候,会遇到两个问题: 1)scroll view上放menu时,如果拖动menu scroll view不会被拖动,如果scroll view上全是按钮就几乎没地方可以拖动了。 2)当menu item滚动出scrollview的可视区域时,仍然能被触发。 这两个问题确实
在使用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是一个冗余量,避免手抖按不上的问题:) 这个问题的原因是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里面有一句: touchListener->setSwallowTouches( true );
所以花了几分钟自己写了个继承Menu的类, 修改下方法,然后要使用Menu的地方替换为自己的DBMenu就完美解决问题了。 下面是源码头h文件: #pragma once #include cocos2d.h USING_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.h bool 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:start void 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传过去就行了。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |