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

在使用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里面有一句:
touchListener->setSwallowTouches(true);
将true修改为false后,完全木有问题。

所以花了几分钟自己写了个继承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传过去就行了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是