源碼版本來自3.1rc
轉載請註明
cocos2d-x源碼分析總目錄
http://blog.csdn.net/u011225840/article/details/31743129
1.繼承結構
control的設計整體感覺挺美的,在父類control定義了整個控制事件的基礎以及管理,雖然其繼承了Layer,但其本身和UI組件的實現並沒有關聯。在子類(controlButton,controlSwitch,controlStepper等中實現不同的UI組件)。下面通過源碼來分析control與controlButton,一起來體會下面向對象的魅力。
2.源碼分析
2.1control
看過我Scheduler源碼分析的朋友應該熟悉,Scheduler本身屬於一種Manager,而具體定時的動作來自於CallBackSelector。在control整體的設計中也是如此,control中定義了一系列情況,來訂製合適觸發何種事件,而該事件是否觸發某種Invocation,是可以動態設置的。該Invocation就可以理解爲具體的動作。
2.1.1 EventType
enum class EventType
{
TOUCH_DOWN = 1 << 0, // A touch-down event in the control.
DRAG_INSIDE = 1 << 1, // An event where a finger is dragged inside the bounds of the control.
DRAG_OUTSIDE = 1 << 2, // An event where a finger is dragged just outside the bounds of the control.
DRAG_ENTER = 1 << 3, // An event where a finger is dragged into the bounds of the control.
DRAG_EXIT = 1 << 4, // An event where a finger is dragged from within a control to outside its bounds.
TOUCH_UP_INSIDE = 1 << 5, // A touch-up event in the control where the finger is inside the bounds of the control.
TOUCH_UP_OUTSIDE = 1 << 6, // A touch-up event in the control where the finger is outside the bounds of the control.
TOUCH_CANCEL = 1 << 7, // A system event canceling the current touches for the control.
VALUE_CHANGED = 1 << 8 // A touch dragging or otherwise manipulating a control, causing it to emit a series of different values.
};
開始時,看見如此定義其實有些不懂。但是爲何需要這麼設置呢,這樣可以通過| 操作同時指定兩個Event事件,而如果簡單的使用 1 2 3 4,就不能通過|或者其他操作來唯一確定多個事件。
從上到下,事件分別是在內部觸摸,內部拖動,外部拖動,拖動時進入,拖動時離開,內部鬆開,外部鬆開,取消,值發生改變。
2.1.2 State
enum class State
{
NORMAL = 1 << 0, // The normal, or default state of a control—that is, enabled but neither selected nor highlighted.
HIGH_LIGHTED = 1 << 1, // Highlighted state of a control. A control enters this state when a touch down, drag inside or drag enter is performed. You can retrieve and set this value through the highlighted property.
DISABLED = 1 << 2, // Disabled state of a control. This state indicates that the control is currently disabled. You can retrieve and set this value through the enabled property.
SELECTED = 1 << 3 // Selected state of a control. This state indicates that the control is currently selected. You can retrieve and set this value through the selected property.
};
在control組件下,每一個state都會有對應的UI形態,普通狀態下,UI展示的view可以同被選擇狀態下UI展示的view不同。通過一個map來對應state和UI的存取。
2.1.3 Control Events 的管理
2.1.3.1 sendActionsForControlEvents
觸發對應事件列表的事件action,注意Events是如何表示的(通過bit位的相符,而不是一個list,速度快!)
void Control::sendActionsForControlEvents(EventType controlEvents)
{
//retain和release的作用是保證執行該actions的過程中,control不會被delete。
//可能會有actions會release 事件來源Ref--control,所以需要先retain,保證其執行完所有events後再release。
retain();
// For each control events
for (int i = 0; i < kControlEventTotalNumber; i++)
{
// If the given controlEvents bitmask contains the curent event
//bit位適配
if (((int)controlEvents & (1 << i)))
{
// Call invocations
const auto& invocationList = this->dispatchListforControlEvent((Control::EventType)(1<<i));
for(const auto &invocation : invocationList) {
invocation->invoke(this);
}
}
}
release();
}
Vector<Invocation*>& Control::dispatchListforControlEvent(EventType controlEvent)
{
//這個函數的作用是獲得該類事件類型的InvocationVector
Vector<Invocation*>* invocationList = nullptr;
auto iter = _dispatchTable.find((int)controlEvent);
// If the invocation list does not exist for the dispatch table, we create it
if (iter == _dispatchTable.end())
{
invocationList = new Vector<Invocation*>();
_dispatchTable[(int)controlEvent] = invocationList;
}
else
{
invocationList = iter->second;
}
return *invocationList;
}
2.1.3.2 addTargetWithActionForControlEvents
void Control::addTargetWithActionForControlEvents(Ref* target, Handler action, EventType controlEvents)
{
// For each control events
for (int i = 0; i < kControlEventTotalNumber; i++)
{
// If the given controlEvents bitmask contains the curent event
if (((int)controlEvents & (1 << i)))
{
this->addTargetWithActionForControlEvent(target, action, (EventType)(1<<i));
}
}
}
void Control::addTargetWithActionForControlEvent(Ref* target, Handler action, EventType controlEvent)
{
// Create the invocation object
Invocation *invocation = Invocation::create(target, action, controlEvent);
// Add the invocation into the dispatch list for the given control event
auto& eventInvocationList = this->dispatchListforControlEvent(controlEvent);
//此時pushback的同時也會retain
eventInvocationList.pushBack(invocation);
}
2.1.3.3 removeTargetWithActionForControlEvent
void Control::removeTargetWithActionForControlEvents(Ref* target, Handler action, EventType controlEvents)
{
// For each control events
for (int i = 0; i < kControlEventTotalNumber; i++)
{
// If the given controlEvents bitmask contains the curent event
if (((int)controlEvents & (1 << i)))
{
this->removeTargetWithActionForControlEvent(target, action, (EventType)(1 << i));
}
}
}
void Control::removeTargetWithActionForControlEvent(Ref* target, Handler action, EventType controlEvent)
{
// Retrieve all invocations for the given control event
//<Invocation*>
auto& eventInvocationList = this->dispatchListforControlEvent(controlEvent);
//remove all invocations if the target and action are null
//TODO: should the invocations be deleted, or just removed from the array? Won't that cause issues if you add a single invocation for multiple events?
if (!target && !action)
{
//remove objects
eventInvocationList.clear();
}
else
{
std::vector<Invocation*> tobeRemovedInvocations;
//normally we would use a predicate, but this won't work here. Have to do it manually
for(const auto &invocation : eventInvocationList) {
bool shouldBeRemoved=true;
if (target)
{
shouldBeRemoved=(target==invocation->getTarget());
}
if (action)
{
shouldBeRemoved=(shouldBeRemoved && (action==invocation->getAction()));
}
// Remove the corresponding invocation object
if (shouldBeRemoved)
{
tobeRemovedInvocations.push_back(invocation);
}
}
for(const auto &invocation : tobeRemovedInvocations) {
eventInvocationList.eraseObject(invocation);
}
}
}
在介紹controlbutton之前,我必須要再次強調下control源碼關於事件類型和狀態的處理:使用bit位是否match可以唯一確定存,並可以消除使用list的影響,適合於enum類比較少且需要同時傳遞多個的情況。
2.2 ControlButton
2.2.1 Touch
對於controlButton,何時觸發什麼事件是在觸摸機制中決定的,通過分析其源碼可以很好看出。
bool ControlButton::onTouchBegan(Touch *pTouch, Event *pEvent)
{
//是否接收該touch
if (!isTouchInside(pTouch) || !isEnabled() || !isVisible() || !hasVisibleParents() )
{
return false;
}
//感覺這段與hasVisibleParents重複了,可以刪除
for (Node *c = this->_parent; c != nullptr; c = c->getParent())
{
if (c->isVisible() == false)
{
return false;
}
}
_isPushed = true;
this->setHighlighted(true);
//touch down事件只在began中觸發
sendActionsForControlEvents(Control::EventType::TOUCH_DOWN);
return true;
}
void ControlButton::onTouchMoved(Touch *pTouch, Event *pEvent)
{
if (!isEnabled() || !isPushed() || isSelected())
{
if (isHighlighted())
{
setHighlighted(false);
}
return;
}
bool isTouchMoveInside = isTouchInside(pTouch);
//在inside內部move並且當前狀態不是highlight,說明從外部移入到內部,觸發事件drag enter
if (isTouchMoveInside && !isHighlighted())
{
setHighlighted(true);
sendActionsForControlEvents(Control::EventType::DRAG_ENTER);
}
//inside內部move並且當前狀態時highlight,說明在內部移動,觸發事件 drag inside
else if (isTouchMoveInside && isHighlighted())
{
sendActionsForControlEvents(Control::EventType::DRAG_INSIDE);
}
//outside move 但是當前狀態是highlight,證明從內移動到外,觸發事件drag exit
else if (!isTouchMoveInside && isHighlighted())
{
setHighlighted(false);
sendActionsForControlEvents(Control::EventType::DRAG_EXIT);
}
//outside move 並且 不是highlight 在外部移動,觸發事件 drag outside
else if (!isTouchMoveInside && !isHighlighted())
{
sendActionsForControlEvents(Control::EventType::DRAG_OUTSIDE);
}
}
void ControlButton::onTouchEnded(Touch *pTouch, Event *pEvent)
{
_isPushed = false;
setHighlighted(false);
//在這裏其實應該增加判斷的,對於controlButton放在scrollView或者tableView或者可以移動的layer上的時候
//應該給用戶一個開關選擇,根據移動了距離的多少判斷用戶是否要觸發touch up inside和 outside 事件。
if (isTouchInside(pTouch))
{
sendActionsForControlEvents(Control::EventType::TOUCH_UP_INSIDE);
}
else
{
sendActionsForControlEvents(Control::EventType::TOUCH_UP_OUTSIDE);
}
}
void ControlButton::onTouchCancelled(Touch *pTouch, Event *pEvent)
{
_isPushed = false;
setHighlighted(false);
sendActionsForControlEvents(Control::EventType::TOUCH_CANCEL);
}
2.2.2 create and needlayout
control button 本質是一個label與一個scale9sprite,在其初始化中可以看出。
2.2.2.1 create相關
bool ControlButton::initWithLabelAndBackgroundSprite(Node* node, Scale9Sprite* backgroundSprite)
{
if (Control::init())
{
CCASSERT(node != nullptr, "Label must not be nil.");
LabelProtocol* label = dynamic_cast<LabelProtocol*>(node);
CCASSERT(backgroundSprite != nullptr, "Background sprite must not be nil.");
CCASSERT(label != nullptr || backgroundSprite != nullptr, "");
_parentInited = true;
_isPushed = false;
// Adjust the background image by default
setAdjustBackgroundImage(true);
setPreferredSize(Size::ZERO);
// Zooming button by default
_zoomOnTouchDown = true;
_scaleRatio = 1.1f;
// Set the default anchor point
ignoreAnchorPointForPosition(false);
setAnchorPoint(Vec2::ANCHOR_MIDDLE);
// Set the nodes,label
setTitleLabel(node);
setBackgroundSprite(backgroundSprite);
// Set the default color and opacity
setColor(Color3B::WHITE);
setOpacity(255.0f);
setOpacityModifyRGB(true);
// Initialize the dispatch table,開始時候的狀態皆爲normal
setTitleForState(label->getString(), Control::State::NORMAL);
setTitleColorForState(node->getColor(), Control::State::NORMAL);
setTitleLabelForState(node, Control::State::NORMAL);
setBackgroundSpriteForState(backgroundSprite, Control::State::NORMAL);
setLabelAnchorPoint(Vec2::ANCHOR_MIDDLE);
// Layout update
needsLayout();
return true;
}
//couldn't init the Control
else
{
return false;
}
}
std::unordered_map<int, std::string> _titleDispatchTable;
std::unordered_map<int, Color3B> _titleColorDispatchTable;
Map<int, Node*> _titleLabelDispatchTable;
Map<int, Scale9Sprite*> _backgroundSpriteDispatchTable;
並且通過一系列get set函數將狀態與這些屬性相關聯,具體的不再贅述。
2.2.2.2 needlayout
void ControlButton::needsLayout()
{
//整體步驟:獲取特定狀態下的label和sprite,然後將button的size設置爲兩者的最大值,然後顯示兩者
if (!_parentInited) {
return;
}
// Hide the background and the label
if (_titleLabel != nullptr) {
_titleLabel->setVisible(false);
}
if (_backgroundSprite) {
_backgroundSprite->setVisible(false);
}
// Update anchor of all labels
this->setLabelAnchorPoint(this->_labelAnchorPoint);
// Update the label to match with the current state
_currentTitle = getTitleForState(_state);
_currentTitleColor = getTitleColorForState(_state);
this->setTitleLabel(getTitleLabelForState(_state));
LabelProtocol* label = dynamic_cast<LabelProtocol*>(_titleLabel);
if (label && !_currentTitle.empty())
{
label->setString(_currentTitle);
}
if (_titleLabel)
{
_titleLabel->setColor(_currentTitleColor);
}
if (_titleLabel != nullptr)
{
_titleLabel->setPosition(Vec2 (getContentSize().width / 2, getContentSize().height / 2));
}
// Update the background sprite
this->setBackgroundSprite(this->getBackgroundSpriteForState(_state));
if (_backgroundSprite != nullptr)
{
_backgroundSprite->setPosition(Vec2 (getContentSize().width / 2, getContentSize().height / 2));
}
// Get the title label size
Size titleLabelSize;
if (_titleLabel != nullptr)
{
titleLabelSize = _titleLabel->getBoundingBox().size;
}
// Adjust the background image if necessary
if (_doesAdjustBackgroundImage)
{
// Add the margins
if (_backgroundSprite != nullptr)
{
_backgroundSprite->setContentSize(Size(titleLabelSize.width + _marginH * 2, titleLabelSize.height + _marginV * 2));
}
}
else
{
//TODO: should this also have margins if one of the preferred sizes is relaxed?
if (_backgroundSprite != nullptr)
{
Size preferredSize = _backgroundSprite->getPreferredSize();
if (preferredSize.width <= 0)
{
preferredSize.width = titleLabelSize.width;
}
if (preferredSize.height <= 0)
{
preferredSize.height = titleLabelSize.height;
}
_backgroundSprite->setContentSize(preferredSize);
}
}
// Set the content size
//總體來說,需要注意的就是這裏,將兩者size的最大值賦給本身
Rect rectTitle;
if (_titleLabel != nullptr)
{
rectTitle = _titleLabel->getBoundingBox();
}
Rect rectBackground;
if (_backgroundSprite != nullptr)
{
rectBackground = _backgroundSprite->getBoundingBox();
}
Rect maxRect = ControlUtils::RectUnion(rectTitle, rectBackground);
setContentSize(Size(maxRect.size.width, maxRect.size.height));
if (_titleLabel != nullptr)
{
_titleLabel->setPosition(Vec2(getContentSize().width/2, getContentSize().height/2));
// Make visible the background and the label
_titleLabel->setVisible(true);
}
if (_backgroundSprite != nullptr)
{
_backgroundSprite->setPosition(Vec2(getContentSize().width/2, getContentSize().height/2));
_backgroundSprite->setVisible(true);
}
}
3.小結
關於其他control類組件包括:
controlColourPicker:顏色選擇器
controlHuePicker:色調選擇器
controlSwitch:開關
controlSlider:滑塊
controlStepper:計步器
controlPotentioMeter:恆電位儀表。。。(一個圓形儀表,可以旋轉,並且有一個圓形的progress bar)
controlsaturationbrightnessPicker:飽和度亮度選擇器