Source code version from 3.1rc
Reprinted please note
Cocos2d-x source code analysis directory
Http://blog.csdn.net/u011225840/article/details/31743129
1. the overall design of the inherited structure control is quite beautiful. The parent class control defines the basis and management of the entire control event, although it inherits the Layer, however, it is not associated with the implementation of the UI component. Implement different UI components in sub-classes (controlButton, controlSwitch, and controlStepper ). The following uses the source code to analyze control and controlButton to discover the charm of object-oriented technology.
2. source code analysis 2.1 control should be familiar to my Scheduler source code analysis friends. Scheduler itself is a Manager, and the specific scheduled action comes from CallBackSelector. This is also true in the overall design of control. control defines a series of situations to customize the event to be triggered, and whether the event triggers a certain Invocation can be dynamically set. This Invocation can be understood as a specific action.
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. };
At the beginning, I don't understand such definition. But why do we need to set this? We can use the | operation to specify two Event Events at the same time. If we simply use 1 2 3 4, you cannot use | or other operations to uniquely identify multiple events. From top to bottom, the event is in the internal touch, internal drag, external drag, drag into, drag away, internal release, external release, cancel, the value changes.
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. };
Under the control component, each state has a corresponding UI format. In normal state, the view displayed by the UI can be different from the view displayed by the UI in the selected state. Access the state and UI through a map.
2.1.3 Manage Events 2.1.3.1 sendActionsForControlEvents !)
Void Control: sendActionsForControlEvents (EventType controlEvents) {// The role of retain and release is to ensure that the control will not be deleted during the execution of this actions. // Actions may release the event source Ref -- control. Therefore, you must retain the event to ensure that all events are executed before release. Retain (); // For each control events for (int I = 0; I <kControlEventTotalNumber; I ++) {// If the given controlEvents bitmask contains the curent event // bit adaptation 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) {// This function is used to obtain the 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); // at this time, pushback also 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); } }}
Before introducing controlbutton, I must re-emphasize the handling of the event type and status in the control source code: whether to use the bit to match can uniquely determine the storage and eliminate the impact of using the list, this method is suitable for scenarios where there are few enum classes and multiple classes need to be passed at the same time.
2.2 ControlButton 2.2.1 Touch determines when to trigger a controlButton In the Touch mechanism. It can be seen through analyzing its source code.
Bool ControlButton: onTouchBegan (Touch * pTouch, Event * pEvent) {// whether to receive the touch if (! IsTouchInside (pTouch) |! IsEnabled () |! IsVisible () |! HasVisibleParents () {return false;} // you can delete for (Node * c = this-> _ parent; c! = Nullptr; c = c-> getParent () {if (c-> isVisible () = false) {return false ;}}_ isPushed = true; this-> setHighlighted (true); // The touch down event only triggers sendActionsForControlEvents (Control: EventType: TOUCH_DOWN) in began; return true ;}
Void ControlButton: onTouchMoved (Touch * pTouch, Event * pEvent) {if (! IsEnabled () |! IsPushed () | isSelected () {if (isHighlighted () {setHighlighted (false);} return;} bool isTouchMoveInside = isTouchInside (pTouch ); // move inside and the current status is not highlight. It indicates that the event is moved from the external to the internal, and the drag enter if (isTouchMoveInside &&! IsHighlighted () {setHighlighted (true); sendActionsForControlEvents (Control: EventType: DRAG_ENTER) ;}// inside move internally and when the current status is highlight, it indicates moving internally, trigger event drag inside else if (isTouchMoveInside & isHighlighted () {sendActionsForControlEvents (Control: EventType: DRAG_INSIDE);} // outside move but the current status is highlight, the event drag exit else if (! IsTouchMoveInside & isHighlighted () {setHighlighted (false); sendActionsForControlEvents (Control: EventType: DRAG_EXIT);} // outside move and not highlight moves externally, trigger event drag outside else if (! IsTouchMoveInside &&! IsHighlighted () {sendActionsForControlEvents (Control: EventType: DRAG_OUTSIDE );}}
Void ControlButton: onTouchEnded (Touch * pTouch, Event * pEvent) {_ isPushed = false; setHighlighted (false); // you should add a judgment here, when a controlButton is placed on scrollView, tableView, or a layer that can be moved, // You should select a switch, determine whether the user needs to trigger the touch up inside and outside events based on the distance to move. 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 is essentially a label and a scale9sprite. It can be seen in its initialization.
2.2.2.1 create problems
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. the start State is 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 stores state-related information with the view to be displayed on the UI through four maps. TitleDispatch stores the string of the label in different states, titleColor stores the color of the label in different States, and titleLabel stores the Node bound to the title in different States, backgroundsprite is a sprite in different States.
std::unordered_map<int, std::string> _titleDispatchTable; std::unordered_map<int, Color3B> _titleColorDispatchTable; Map<int, Node*> _titleLabelDispatchTable; Map<int, Scale9Sprite*> _backgroundSpriteDispatchTable;
The status is associated with these attributes through a series of get set functions.
2.2.2.2 needlayout
Void ControlButton: needsLayout () {// overall step: Obtain the label and sprite in a specific State, set the size of the button to the maximum value of the two, and then display the 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); LabelPro Tocol * 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: shocould 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 // in general, it is worth noting that here, assign the maximum size of the two to the 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. Summary other control components include: controlColourPicker: color selector controlHuePicker: Tone selector controlSwitch: Switch controlSlider: slider controlStepper: Pedometer controlPotentioMeter: Constant Current Meter... (A circular instrument that can be rotated and has a circular progress bar) controlsaturationbrightnessPicker: Saturation brightness Selector