cocos2d-x 源碼分析 : control 源碼分析 ( 控制類組件 controlButton)

來源:互聯網
上載者:User

源碼版本來自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;    }}

          controlButton通過4個map,將狀態相關的資訊與UI需要顯示的view儲存起來。titleDispatch存放的是不同狀態下label的string,titleColor存放的是不同狀態下label的顏色,titleLabel存放的是不同狀態下title綁定的Node,backgroundsprite是不同狀態下的sprite。     
    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:飽和度亮度選取器 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.