[cocos2d-x v3] TableViewとMenu併用時のタッチイベント

前回は、Pepperでファイルのダウンロードを行いました。
今回は、cocos2d-xのタッチイベントについてのお話です。

■環境
・cocos2d-x ver3.3

TableViewとMenuの併用については、以下の構成を想定します。

この時、タッチ開始箇所がMenu上の場合、TableViewのスクロールを行うことが出来ません。
これは、タッチイベントの「優先度」と「制御設定」によって起こる問題で
cocos2d-xでのタッチイベントの「優先度(Priority)」は、下記のように決まります。

  1. 設定priority値が低いほうが優先
  2. priority値が同じ場合、重なり順(z座標)が手前にあるほうが優先
  3. Nodeが親子関係にある場合、子Nodeが優先

今回のケースで、関係しているのは3番です。
図1を見ても分かるように、MenuはTableViewがもつcellの子Nodeとなるため
タッチイベントが実行される順序は次のようになります。

 Menuタッチイベント→TableViewタッチイベント

この流れだけを見ると、タッチ開始箇所がMenuだった場合でも
TableViewのスクロールが行われるように見えますが
実際にはスクロールはせず、TableViewは静止したままです。

理由としては、リスナーの通知制御設定です。
cocos2d-xのタッチイベントリスナーにはsetSwallowTouchesという関数が用意されており
指定したNodeにのみイベントを発生させる場合はtrue
下のNodeにもイベントを発生させる場合はfalse
を設定することで、タッチイベントの通知を制御することが出来ます。

Menuクラスでは、内部でsetSwallowTouches関数にtrueを設定しており
タッチイベントがTableViewクラスに登録されているリスナーまで通知されないため
下記のように設定することで、タッチ開始箇所がMenuの場合でもスクロールを可能にすることができます。

Menu.cpp

bool Menu::initWithArray(const Vector<MenuItem*>& arrayOfItems)
{
	// 〜省略〜
    
    auto touchListener = EventListenerTouchOneByOne::create();

    // 変更前
    //touchListener->setSwallowTouches(true);

    // 変更後 
    // trueをfalseに変更
    touchListener->setSwallowTouches(false);
    
    touchListener->onTouchBegan = CC_CALLBACK_2(Menu::onTouchBegan, this);
    touchListener->onTouchMoved = CC_CALLBACK_2(Menu::onTouchMoved, this);
    touchListener->onTouchEnded = CC_CALLBACK_2(Menu::onTouchEnded, this);
    touchListener->onTouchCancelled = CC_CALLBACK_2(Menu::onTouchCancelled, this);
    
    _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

	// 〜省略〜        
}

上記設定で、Menu上スクロールには対応できましたが、この状態では不完全です。
スクロール後、タッチエンド時にMenu上に指があった場合、Menuに登録したタッチイベントが実行されてしまいます。
一般的な挙動としては、スクロール後の一発目のエンドイベントではMenuやButtonのタッチイベントは弾きたいところです。
この挙動を実現するためには、TableViewを拡張します。

TableView(ScrollView)クラスでは、ドラッグフラグ用の変数が_touchMovedという名前で宣言されていますが
protectedのため、getter関数を追加します。

CCTableView.h

class CC_EX_DLL TableView : public ScrollView, public ScrollViewDelegate
{
public:
	// 〜省略〜

    //追加
	bool isMoved();

}

CCTableView.cpp

//追加
bool TableView::isMoved()
{
    return _touchMoved;
}

_touchMoved用のgetter関数を追加できましたら、あとはMenuタッチイベント処理のなかで、ドラッグ判定を入れるだけとなります。

TableView *tableView = dynamic_cast<TableView*>( this->getChildByTag( 0 ) );

if ( tableView->isMoved() )
{
    CCLOG( "Moved return" );
    return;
}

以上で、TableViewとMenu併用時のイベント制御についての説明は終了となります。
今回の方法はフレームワークのソースコードを直接修正するオープンソースならではのやり方ですが
バージョンアップの際、その都度修正しなおさなければいけないため
TableViewやMenuを継承した新規クラスを作成し、それらのクラスで今回の設定するのがよいでしょう

最後まで御覧いただきありがとうございました。

<TableView・Menu併用サンプルソース>
AppMain.h

#include "cocos2d.h"
#include "cocos-ext.h"

USING_NS_CC;
USING_NS_CC_EXT;

class AppMain : public cocos2d::Layer, public TableViewDelegate, public TableViewDataSource
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    //=============================
    //	TableView関連
	//=============================
    virtual void scrollViewDidScroll(ScrollView* view){};
    virtual void scrollViewDidZoom(ScrollView* view){};
    virtual Size cellSizeForTable(TableView* table);
    virtual TableViewCell* tableCellAtIndex(TableView* table,ssize_t idx);
    virtual ssize_t numberOfCellsInTableView(TableView* table);
    virtual void tableCellTouched(TableView* table,TableViewCell* cell);
    
    /**
     * メニュータッチイベント
     */
    void onTouchMenu(Ref*sender);
    
    CREATE_FUNC(AppMain);
};

AppMain.cpp


bool AppMain::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    // TableViewを作成
    auto tableView = TableView::create( this, Director::getInstance()->getVisibleSize() );
    tableView->setTag( 0 );
    tableView->setDirection( TableView::Direction::VERTICAL );
    tableView->setVerticalFillOrder( TableView::VerticalFillOrder::TOP_DOWN );
    tableView->setDelegate( this );
    addChild( tableView );
    tableView->reloadData();
    
   
    return true;
}

Size AppMain::cellSizeForTable(TableView *table)
{
    Size visibleSize = Director::getInstance()->getVisibleSize();
    return Size( visibleSize.width, visibleSize.height*.1 );
}

TableViewCell* AppMain::tableCellAtIndex(TableView *table, ssize_t idx)
{
    TableViewCell *cell = table->dequeueCell();
    cell = new TableViewCell();
    cell->autorelease();
    
    //メニュー生成
    auto *defaultSkin = Sprite::create( "button.png" );
    auto *downSkin = Sprite::create( "button.png" );
    downSkin->setColor( Color3B( 120, 120, 120 ) );
    auto *menuItem = MenuItemSprite::create( defaultSkin, downSkin, CC_CALLBACK_1( AppMain::onTouchMenu, this ) );
    Menu* menu = Menu::create( menuItem, NULL );
    cell->addChild( menu );
    
    return cell;
}

void AppMain::onTouchMenu(Ref*sender)
{
    TableView *tableView = dynamic_cast<TableView*>( this->getChildByTag( 0 ) );
    
    if ( tableView->isMoved() )
    {
        CCLOG( "Moved return" );
        return;
    }
    
    CCLOG( "button Touch" );
}

ssize_t AppMain::numberOfCellsInTableView(TableView *table)
{
    return 20;
}

void AppMain::tableCellTouched(TableView* table, TableViewCell* cell)
{
    CCLOG( "cell touch" );
}

弊社では全国各地の請負い(ご自宅)で作業協力頂ける、フリーランスエンジニアの方を常時探しております。
ご興味ある方は、お気軽にお問い合わせ下さい。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*