• cocos2d-x 源代码分析 : control 源代码分析 ( 控制类组件 controlButton)


    源代码版本号来自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:饱和度亮度选择器
     

  • 相关阅读:
    数模竞赛-长三角旅游路线
    数模培训-高压油管
    数模培训-城市表层土壤重金属污染问题
    数模练习-养孩子
    SpringBoot01
    搬家啦
    P4655 [CEOI2017]Building Bridges
    深入理解斜率优化
    暴力 K 短路的一个小细节
    搜索优化
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10826769.html
  • Copyright © 2020-2023  润新知