編輯:關於Android編程
在移動平台中設備與用戶的交互必須通過事件處理完成。用戶輸入被封裝為事件,Cocos2d-x游戲引擎能夠接收並處理這些事件,包括觸摸事件、鍵盤事件、鼠標事件、加速度事件和自定義事件等。這裡需要注意,不同平台之間事件略有差異,這與硬件有關,如在iOS
平台就無法接收鍵盤事件,在PC和Mac OS X平台無法接收觸摸事件和加速度事件。
Cocos2d-x 用戶事件處理機制
一般來說,事件處理機制都有三個角色:事件、事件源和事件處理,其中事件源是事件發生的場所,通常指控件或視圖;事件處理者是接收事件並對其進行響應處理。
(1) 事件:
事件類是Event,它的子類主要有:
EventTouch(觸摸事件)
EventKeyBoard(鍵盤事件)
EventMouth(鼠標事件)
EventAcceleration(加速度事件)
EventCustom(自定義事件)
(2) 事件源:
指的是Cocos2d-x中的精靈、層和菜單等節點對象。
(3) 事件處理:
Cocos2d-x中的事件處理者是事件監聽類EventListener,它的子類主要有:
EventListenerTouchOneByOne(單點觸摸監聽器)
EventListenerTouchAllAtOnce(多點觸摸監聽器)
EventListenerKeyBoard(鍵盤事件監聽器)
EventListenerMouse(鼠標事件監聽器)
EventListenerAcceleration(加速度事件監聽器)
EventListenerCustom(自定義事件監聽器)
1.事件分發器
事件的監聽和事件具有對應關系,Cocos2d-x提供了一個事件分發器(EventDispatcher)用於管理這種關系。
EventDispatcher類采用的是單例模式,可以通過Director::getInstance()->getEventDispatcher()獲得事件分發對象。
EventDispatcher類中注冊事件監聽器到事件的函數如下:
(1)指定固定的事件優先級注冊監聽器,事件優先級決定事件響應的優先級,值越小優先級越高。
void addEventListenerWithFixedPriority(EventListener * listener, int fixedPriority)
(2)把一些控件的顯示優先級作為事件優先級,越上層的精靈或層越先響應事件。
void addEventListenerWithSceneGraphPriority(EventListener * listener, Node * node)
當不再進行事件響應時,需要注銷事件監聽器。主要的注銷函數如下:
(1)注銷指定事件監聽器
void removeEventListener(EventListener * listener)
(2)注銷自定義事件監聽器
void removeCustomEventListener(EventListener * listener)
(3)注銷所有事件監聽,注意:使用該函數之後,菜單也不能響應事件了,因為它需要接收觸摸事件。
void removeAllEventListeners(EventListener * listener)
2.事件
下面主要介紹五類事件:
2.1 觸摸事件
觸摸事件可以有“按下”、“移動”和“抬起”的階段,表示觸摸的開始、移動和結束狀態。另外,觸摸事件的不同階段都有單點觸摸和多點觸摸,具體還得看平台和設備。
(1)EventListenerTouchOneByOne(單點觸摸監聽器)
單點觸摸事件中的響應屬性:
1)當一個手指觸摸屏幕時回調該屬性的指定函數。如果返回為true,則可以回調後面兩個屬性所指定的函數,否則不回調。
std::functiononTouchBegan
2)當一個手指在屏幕移動時回調該屬性所指定的函數。
std::functiononTouchMoved
3)當一個手指離開屏幕時回調該屬性所指定的函數。
std::functiononTouchEnded
4)當單點觸摸事件被取消時回調該屬性所指定的函數
std::functiononTouchCancelled
(2)EventListenerTouchAllAtOnce(多點觸摸監聽器)
多點觸摸事件中的響應屬性:
1)當多個手指觸摸屏幕時回調該屬性的指定函數。如果返回為true,則可以回調後面兩個屬性所指定的函數,否則不回調。
std::function&,Event *)> onTouchesBegan
2)當多個手指在屏幕移動時回調該屬性所指定的函數。
std::function&,Event *)> onTouchesMoved
3)當多個手指離開屏幕時回調該屬性所指定的函數。
std::function&,Event *)> onTouchesEnded
4)當多點觸摸事件被取消時回調該屬性所指定的函數
std::function&,Event *)> onTouchesCancelled
另外,每個觸摸點(touch)對象包含了當前位置信息,以及之前的位置信息。
下面的函數是可以獲得觸摸點之前的位置信息:
Vec2 getPreviousLocationInView() //UI坐標 Vec2 getPreviousLocation() //OpenGL坐標
下面的函數是可以獲得觸摸點當前的位置信息:
Vec2 getLocationInView() //UI坐標 Vec2 getLocation() //OpenGL坐標
接下來是一個簡單單點觸摸示例:
//一般來說監聽器在onEnter時注冊 void HelloWorld::onEnter() { Layer::onEnter(); auto listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegan, this); listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMoved, this); listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnded, this); EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher(); //參數為Node類型,故需要用getChildByTag函數獲取節點 eventDispatcher->addEventListenerWithGraphPriority(listener, getChildByTag(tagA)); /* 當一個事件監聽器需要重復利用時,需要用clone()方法創建一個新的克隆,因為在使用addEventListenerWithSceneGraphPriorith或者addEventListenerWithFixedPriority方法進行注冊時,會對當前使用的事件監聽器添加一個已注冊的標記,這使得它不能夠被添加多次。 */ eventDispatcher->addEventListenerWithGraphPriority(listener->clone(), getChildByTag(tagB)); eventDispatcher->addEventListenerWithGraphPriority(listener->clone(), getChildByTag(tagC)); } bool HelloWorld::touchBegan(Touch * touch, Event * event) { auto target = static_cast(event->getCurrentTarget()); Vec2 locationInNode = target->convertToNodeSpace(touch->getLocation()); Size s = target->getContentSize(); Rect rect = Rect(0, 0, s.width, s.height); //判斷單擊是否在范圍內 if(rect.containsPoint(locationInNode)) { target->runAction(ScaleBy::create(1.5f, 1.5f)); ... return true;//吞沒事件 } return false; } void HelloWorld::touchMoved(Touch * touch, Event * event) { auto target = static_cast (event->getCurrentTarget()); target->setPosition(target->getPosition() + touch->getDelta()); } void HelloWorld::touchEnded(Touch * touch, Event * event) { auto target = static_cast (event->getCurrentTarget()); Vec2 locationInNode = target->convertToNodeSpace(touch->getLocation()); Size s = target->getContentSize(); Rect rect = Rect(0, 0, s.width, s.height); if(rect.containsPoint(locationInNode)) { target->runAction(ScaleTo::create(1.0f, 1.0f)); ... return true; } }
其中,CC_CALLBACK_2(HelloWorld::touchBegan, this) 是使用CC_CALLBACK_2宏綁定回調函數。
2.2 鍵盤事件
鍵盤事件監聽器是EventListenerKeyboard,其中的事件響應屬性有:
(1)按鍵按下時回調該屬性所指定函數。
std::functiononKeyPressed
(2)按鍵抬起時回調該屬性所指定函數。
std::functiononKeyReleased
鍵盤事件處理的代碼片段如下,這裡使用Lambda表達式,之後會介紹:
void HelloWorld::onEnter() { Layer::onEnter(); auto listener = EventListenerKeyboard::create(); //EventKeyboard::KeyCode 是按鍵號 listener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event * event) { ... }; listener->onKeyReleased = [](EventKeyboard::KeyCode keyCode, Event * event) { ... }; EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->addEventListenerWithGraphPriority(listener, this); }
2.3 鼠標事件
鼠標事件監聽器是EventListenerMouse,其中的事件響應屬性有:
(1)當鼠標鍵按下時回調該屬性所指定的函數。
std::functiononMouseDown
(2)當鼠標移動時回調該屬性所指定的函數。
std::functiononMouseMove
(3)當鼠標滾動時回調該屬性所指定的函數。
std::functiononMouseScroll
(4)當鼠標鍵抬起時回調該屬性所指定的函數。
std::functiononMouseUp
鼠標事件處理的代碼片段如下,這裡使用Lambda表達式,之後會介紹:
void HelloWorld::onEnter() { Layer::onEnter(); auto listener = EventListenerMouse::create(); listener->onMouseMove = [](Event * event) { EventMouse * e = (EventMouse *)event; ... }; listener->onMouseUp = [](Event * event) { EventMouse * e = (EventMouse *)event; ... }; listener->onMouseScroll = [](Event * event) { EventMouse * e = (EventMouse *)event; ... }; listener->onMouseDown = [](Event * event) { EventMouse * e = (EventMouse *)event; ... }; EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->addEventListenerWithGraphPriority(listener, this); }
2.4 加速度事件
大多數的移動設備都支持加速度計傳感器,加速度計傳感器是一種能夠感知設備一個方向上線性加速度的傳感器。iOS和安卓設備三軸加速度計的坐標系是右手坐標系。
加速度計三維坐標系
使用事件分發器之前需要注冊加速度事件監聽器,在此之前還需先啟用此設備硬件設備:
Device::setAccelerometerEnabled(true);
加速度事件監聽器是EventListenerAcceleration,與其他事件監聽器不同的是,它沒有事件響應屬性,事件響應是通過靜態create指定的。代碼如下:
static EventListenerAcceleration * create(std::functioncallback)
下面是示例片段:
void HelloWorld::onEnter() { Layer::onEnter(); Device::setAccelerometerEnabled(true); auto listener = EventListenerAcceleration::create([](Acceleration * acc, Event * event) { ... }); EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->addEventListenerWithGraphPriority(listener, this); }
2.5 自定義事件
上面的事件類型是系統已經定義了的,而且事件(例如觸摸屏幕,按鍵響應)是由系統自動觸發的。另外,你可以制作你自己的custom events ,它們可以不由系統觸發,但是必須通過你的代碼,如下:
_listener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event){ std::string str("Custom event 1 received, "); char* buf = static_cast(event->getUserData()); str += buf; str += " times"; statusLabel->setString(str.c_str()); }); _eventDispatcher->addEventListenerWithFixedPriority(_listener, 1);
上面自定義了一個事件監聽器,並且帶有一個響應方法。它被添加到了事件分發器裡。
static int count = 0; ++count; char* buf = new char[10]; sprintf(buf, "%d", count); EventCustom event("game_custom_event1"); event.setUserData(buf); _eventDispatcher->dispatchEvent(&event); CC_SAFE_DELETE_ARRAY(buf);
上面的例子創建了一個 EventCustom 對象,並且設置了他的 UserData。之後,它被 _eventDispatcher->dispatchEvent(&event);手動的分發。它將觸發之前定義好的事件處理方法。
3.Lambda表達式
在Cocos2d-x 3.0之後提供了對C++11標准的支持,其中的Lambda表達式使用起來十分簡潔。
對上一節的代碼進行替換:
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this); ... bool HelloWorld::onTouchBegan(Touch * touch, Event * event) { .... return false; }
替換為:
listener->onTouchBegan = [](Touch * touch, Event * event) { ... return false; };
上面語句中的[](Touch * touch, Event * event){…}就是Lambda表達式,Lambada表達式就是JavaScript語言中的匿名函數,它可以直接聲明函數而不需要獨立聲明。
替換為Lambda表達式之後之前的頭文件代碼也可以修改的比較簡潔。
4.在層中進行的事件處理
層是節點的派生類,可以在層中響應事件處理,Cocos2d-x中為Layer又定義了一些與事件相關的函數。
單點觸摸相關函數:
(1)bool onTouchBegan(Touch * touch, Event * event) (2)void onTouchCancelled(Touch * touch, Event * event) (3)void onTouchEnded(Touch * touch, Event * event) (4)void onTouchMoved(Touch * touch, Event * event)
多點觸摸相關函數:
(1)void onTouchesBegan(const std::vector&touches, Event * event) (2)void onTouchesCancelled(const std::vector &touches, Event * event) (3)void onTouchesEnded(const std::vector &touches, Event * event) (4)void onTouchesMoved(const std::vector &touches, Event * event)
鍵盤事件相關函數:
(1)void onKeyPressed(EventKeyboard::KeyCode keyCode, Event * event) (2)void onKeyReleased(EventKeyboard::KeyCode keyCode, Event * event)
加速度事件相關函數:
void onAcceleration(Acceleration * acc, Event * event)
注意,若使用層的事件處理,需要在層的頭文件中定義相關函數,由於是重寫,需要在前面加上virtual關鍵字,且函數名需與父類一致。
有關事件的初始化可以放在onEnter函數或者init函數中,還需加入以下代碼:
//使層開始支持觸摸事件 setTouchEnabled(true); //設置單點觸摸,多點觸摸是Touch::DispatchMode::ALL_AT_ONCE setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
層中的觸摸事件使用起來比較簡單,由於事件監聽的對象是層,而不是層中的精靈等對象,當判斷是否單擊了哪個精靈對象時比較麻煩,需要使用Rect的containsPoint函數逐一判斷每個精靈對象。
可以看出,層中的事件處理比較簡單,不需要注冊事件監聽器,不需要指定它的事件響應優先級,這種優點也是它的缺點,當處理多個精靈的觸摸事件時就有些繁瑣。
總結
我們了解了Cocos2d-x的用戶輸入事件處理,包括五類事件的注冊和處理,希望能夠很好的在開發過程中使用。
Fragment 是個什麼東西?可以把Fragment理解成Activity中的模塊,這個模塊有自己的布局,有自己的生命周期,單獨處理自己的輸入,在Activity運行的
大家知道,自定義View有三個重要的步驟:measure,layout,draw。而measure處於該鏈條的首端,占據著極其重要的地位;然而對於measur
一、ToolBar1、在build.gradle中添加依賴,例如:compile com.android.support:appcompat-v7:23.4.02、去掉應
View的Draw時序圖前面幾篇通過對View樹的measure和layout過程分析事,接下來將結合前兩步得到的測量值及在視圖中的位位置,開始進行繪制操作,一步比一步復
1.TextView Textview在之前的學習中用到過好多