はじめに
いわゆる2度押し防止というものですかね。
そのやり方について考えてみたのでメモします。
私が思いついたのは以下の2つです。
- アクション実行中はイベントを無効化する。
- シーングラフの最上位にイベント制御のための不可視のノードを置く。
サンプルがないと説明しにくいと思ったので作りました(Cocos2d-x v3.5)。
デフォルトのHelloWorld画面に以下のような動作を追加しています。
- 画面をタップすると画像が180度回転する(最初は右回り)。
- 再度タップすると逆方向に180度回転する。
2度押し防止処理をしない場合
サンプルは以下です。
アクション実行部分は以下のようになっています。
// HelloWorldScene.cpp
void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
log("=== Call HelloWorld::onTouchEnded() ===");
// Create Action
float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
auto rotateBy = RotateBy::create(2.0f, deltaAngle);
// Run Action
this->getSprite()->runAction(rotateBy);
// Set RightFlg
this->setRightFlg(!this->getRightFlg());
}
これだと回転中にタッチすると、そこから逆回転してしまいます。
2度押し防止対策を行い、回転中にはタッチが無効になるようにしたいと思います。
アクション実行中はイベントを無効化する
アクション実行前にイベントリスナーを無効化できれば、二度押し防止が実現できそうです。
イベントリスナーの有効化・無効化はEventListenerクラスから継承しているsetEnabledメソッドで行えます。
アクション実行後にイベントリスナーを再度有効化するのも忘れずに行いましょう。
なお、イベントリスナーはEventDispatcherで登録後に取得する方法が見当たらないため、メンバ変数として保持します。
サンプルは以下です。
ポイント箇所を抜粋します。
// HelloWorldScene.cpp
// ...
bool HelloWorld::init()
{
// Event Listener
auto eventListener = EventListenerTouchOneByOne::create();
eventListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
eventListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener, this);
// Set Properties
// ...
this->setEventListener(eventListener);
return true;
}
// ...
void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
log("=== Call HelloWorld::onTouchEnded() ===");
// Create Action
auto eventListenerDisable = CallFunc::create([&]() {
log("eventListenerDisable()");
this->getEventListener()->setEnabled(false);
});
auto eventListenerEnable = CallFunc::create([&]() {
log("eventListenerEnable()");
this->getEventListener()->setEnabled(true);
});
float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
auto rotateBy = RotateBy::create(2.0f, deltaAngle);
// Run Action
this->getSprite()->runAction(Sequence::create(eventListenerDisable,
rotateBy,
eventListenerEnable,
nullptr));
// Set RightFlg
this->setRightFlg(!this->getRightFlg());
}
Sequenceアクションを使い、目的のアクションの実行前後でイベントリスナーの無効化・有効化を行っています。
これにより、回転中にタッチしても、逆回転しなくなりました。
シーングラフの最上位にイベント制御のための不可視のノードを置く
前述の方法だと、回転中にタッチしても逆回転は行われませんが、右下のメニューボタンは押すことが出来てしまいます。
全てのイベントリスナーを無効にしたい場合は、イベント制御のための不可視のノードを追加します。
サンプルは以下です。
ポイント箇所を抜粋します。
// HelloWorldScene.cpp
// ...
bool HelloWorld::init()
{
// ...
// Invisible node to prevent event listener
auto invisibleNode = Node::create();
this->addChild(invisibleNode, 999);
auto preventEventListener = EventListenerTouchOneByOne::create();
preventEventListener->onTouchBegan = [](Touch* touch, Event* event) {
log("!!! Prevent Event Listener !!!");
return true;
};
preventEventListener->setSwallowTouches(true);
preventEventListener->setEnabled(false);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(preventEventListener, invisibleNode);
// Set Properties
// ...
this->setPreventEventListener(preventEventListener);
return true;
}
// ...
void HelloWorld::onTouchEnded(Touch* touch, Event* event)
{
log("=== Call HelloWorld::onTouchEnded() ===");
// Create Action
auto eventListenerDisable = CallFunc::create([&]() {
log("eventListenerDisable()");
this->getPreventEventListener()->setEnabled(true);
});
auto eventListenerEnable = CallFunc::create([&]() {
log("eventListenerEnable()");
this->getPreventEventListener()->setEnabled(false);
});
float deltaAngle = this->getRightFlg() ? 180.0f : -180.0f;
auto rotateBy = RotateBy::create(2.0f, deltaAngle);
// Run Action
this->getSprite()->runAction(Sequence::create(eventListenerDisable,
rotateBy,
eventListenerEnable,
nullptr));
// Set RightFlg
this->setRightFlg(!this->getRightFlg());
}
不可視のノードを追加し、シーングラフの優先度がもっとも高くなるようにZオーダーを設定します。
そのノードに対し、setSwallowTouches()をtrueに設定したイベントリスナーを登録します。
そして、アクションの前後にそのイベントリスナーの有効化・無効化を行えば、アクション実行中は全てのイベントリスナーを無効化できます。
なお、ここではイベントリスナーのsetEnabled()を切り替えて実現しましたが、setSwallowTouches()のほうを切り替えても可能だと思います。
さて、ここで一つ疑問が。
ノードを置かずに、addEventListenerWithFixedPriority()で優先度が最高のイベントリスナーを設定するだけでも出来るのでは…?
と思ってそれも試したのですが、なぜか出来ませんでした。。。
イベントリスナー自体は有効になっているものの、どうもsetSwallowTouches()の設定が効いてないっぽく、イベントが伝搬してしまうんですよね…。
うーむ。。。
最後に
1つのイベントリスナーを無効化したい場合は前者、全てのイベントリスナーを無効化したい場合は後者、といった使い分けになると思います。