Source Code Analysis of cocos2d-x CCScrollView

Posted by rbama on Tue, 04 Jun 2019 00:48:07 +0200

Original address: http://blog.csdn.net/u011225840/article/details/30033501


1. Inheritance tree structure



You can see that CCScrollView is essentially a kind of CCLayer with all the properties and methods of the layer.Source analysis for CCLayer will follow.

2. Important Members

 1.  CCScrollViewDelegate* m_pDelegate;
In cocos2d-x, many delegates are used.Here's a brief explanation of delegate.(As for the difference between delegate and proxy, please refer to the three proxy cases in headfirst first first, then you can distinguish between google, which will not be repeated here.)
XXXDelegate encapsulates interfaces (the implementation in c++ is a virtual function and a pure virtual function that must be implemented). There are some methods in class A, such as getDataNum in View. View determines how an interface is displayed based on the amount of data, but A has no direct relationship with data.So, in View getDataNum in A calls the DataDelegate method inside A to get how much data it has.As to what this data is, you only need to implement a specific class of DataDelegate.This way, the coupling between the View and the data is very low.View only relies on abstract DataDelegate.
In subsequent source analysis, you can see the beauty of delegate.


3. Source Code Parsing

3.1 ccTouchBegan


For the important part of ccTouchBegan, I added a comment.As you can see from the code, CCscrollView supports single and double touch.
  1. bool CCScrollView::ccTouchBegan(CCTouch* touch, CCEvent* event)  
  2. {  
  3.     if (!this->isVisible() || !m_bCanTouch)  
  4.     {  
  5.         return false;  
  6.     }  
  7.       
  8.     CCRect frame = getViewRect();  
  9.   
  10.     //dispatcher does not know about clipping. reject touches outside visible bounds.  
  11.     /* 
  12.     1. ccScrollView Only up to two touch points are allowed and more than two will not be considered touch. 
  13.     2. When the CCScrollView is in a moving state, new touches that occur in this state will not be considered to occur. 
  14.     3.Note that frame is not the current size, but the frame of the current ViewSize, that is, the touch point must be identified as touch within the displayed Rect (size can be set via setViewSize) 
  15.    */  
  16.     if (m_pTouches->count() > 2 ||  
  17.         m_bTouchMoved          ||  
  18.         !frame.containsPoint(m_pContainer->convertToWorldSpace(m_pContainer->convertTouchToNodeSpace(touch))))  
  19.     {  
  20.         m_pTouches->removeAllObjects();  
  21.         return false;  
  22.     }  
  23.   
  24.     if (!m_pTouches->containsObject(touch))  
  25.     {  
  26.         m_pTouches->addObject(touch);  
  27.     }  
  28.     //CCLOG("CCScrollView::ccTouchBegan %d", m_pTouches->count());  
  29.     /* 
  30.      Set the properties of single touch when the touch point is 1.In particular, the m_bDragging attribute indicates that the touch behavior is dragging 
  31.    */  
  32.     if (m_pTouches->count() == 1)  
  33.     { // scrolling  
  34.         m_tTouchPoint     = this->convertTouchToNodeSpace(touch);  
  35.         m_bTouchMoved     = false;  
  36.         m_bDragging     = true//dragging started  
  37.         m_tScrollDistance = ccp(0.0f, 0.0f);  
  38.         m_fTouchLength    = 0.0f;  
  39.     }  
  40.     /* 
  41.         Set the properties of double touch when the number of touch points is 2 
  42.     */  
  43.     else if (m_pTouches->count() == 2)  
  44.     {  
  45.         m_tTouchPoint  = ccpMidpoint(this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),  
  46.                                    this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));  
  47.         m_fTouchLength = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),  
  48.                                    m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));  
  49.         m_bDragging  = false;  
  50.     }   
  51.     return true;  
  52. }  

3.2 ccTouchMoved

Same as above, with comments in the code
  1. void CCScrollView::ccTouchMoved(CCTouch* touch, CCEvent* event)  
  2. {  
  3.       
  4.     if (!this->isVisible())  
  5.     {  
  6.         return;  
  7.     }  
  8.   
  9.     /* 
  10.         If scrolling is not allowed at this time, exit.This can be set through the set function.Default to false 
  11.     */  
  12.     if(this->m_bScrollLock)  
  13.     {  
  14.         return;  
  15.     }  
  16.   
  17.     if (m_pTouches->containsObject(touch))  
  18.     {  
  19.         /* 
  20.             Ah oh, it's fun. 
  21.             When scrolling 
  22.         */  
  23.         if (m_pTouches->count() == 1 && m_bDragging)  
  24.         { // scrolling  
  25.             CCPoint moveDistance, newPoint, maxInset, minInset;  
  26.             CCRect  frame;  
  27.             float newX, newY;  
  28.               
  29.             frame = getViewRect();  
  30.             //Get the coordinates of the current point, and get the distance between the current point and the last touch point (moveDistance is also CCPoint, x and Y is the distance between the current point and the last point, y distance)  
  31.             newPoint     = this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0));  
  32.             moveDistance = ccpSub(newPoint, m_tTouchPoint);  
  33.               
  34.             float dis = 0.0f;  
  35.             //If there is a direction constraint, get the corresponding distance based on the direction constraint  
  36.             if (m_eDirection == kCCScrollViewDirectionVertical)  
  37.             {  
  38.                 dis = moveDistance.y;  
  39.             }  
  40.             else if (m_eDirection == kCCScrollViewDirectionHorizontal)  
  41.             {  
  42.                 dis = moveDistance.x;  
  43.             }  
  44.             else  
  45.             {  
  46.                 dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y);  
  47.             }  
  48.   
  49.             //If the moving distance is too short, no movement is judged  
  50.             if (!m_bTouchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH )  
  51.             {  
  52.                 //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y);  
  53.                 return;  
  54.             }  
  55.             //For the first move, set the moveDistance to 0  
  56.             if (!m_bTouchMoved)  
  57.             {  
  58.                 moveDistance = CCPointZero;  
  59.             }  
  60.               
  61.             m_tTouchPoint = newPoint;  
  62.             m_bTouchMoved = true;  
  63.             //Point must be inside viewRect  
  64.             if (frame.containsPoint(this->convertToWorldSpace(newPoint)))  
  65.             {  
  66.                 //Set moveDistance based on movable direction  
  67.                 switch (m_eDirection)  
  68.                 {  
  69.                     case kCCScrollViewDirectionVertical:  
  70.                         moveDistance = ccp(0.0f, moveDistance.y);  
  71.                         break;  
  72.                     case kCCScrollViewDirectionHorizontal:  
  73.                         moveDistance = ccp(moveDistance.x, 0.0f);  
  74.                         break;  
  75.                     default:  
  76.                         break;  
  77.                 }  
  78.                 //This version is useless.  
  79.                 maxInset = m_fMaxInset;  
  80.                 minInset = m_fMinInset;  
  81.   
  82.                 //Get the new coordinates of the container, notice it's the container  
  83.                 newX     = m_pContainer->getPosition().x + moveDistance.x;  
  84.                 newY     = m_pContainer->getPosition().y + moveDistance.y;  
  85.                 //Rolling CPoint Vector Settings  
  86.                 m_tScrollDistance = moveDistance;  
  87.                 this->setContentOffset(ccp(newX, newY));  
  88.             }  
  89.         }  
  90.         //The effect is zooming when two-touch is applied, and len is the distance at which the two-touch is moved each time.  
  91.         //m_fTouchLength, on the other hand, is the distance at the beginning of the two points, scaling based on the ratio of the distance to the initial distance during the move process  
  92.         else if (m_pTouches->count() == 2 && !m_bDragging)  
  93.         {  
  94.             const float len = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),  
  95.                                             m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));  
  96.             this->setZoomScale(this->getZoomScale()*len/m_fTouchLength);  
  97.         }  
  98.     }  
  99. }  
On a single touch, a function called setContentOffset was called.Continue to analyze contentOffset below.

setContentOffset

  1. void CCScrollView::setContentOffset(CCPoint offset, bool animated/* = false*/)  
  2. {  
  3.     //Default, no processing  
  4.     if (animated)  
  5.     { //animate scrolling  
  6.         this->setContentOffsetInDuration(offset, BOUNCE_DURATION);  
  7.     }   
  8.     //Fun stuff  
  9.     else  
  10.     { //set the container position directly  
  11.         //Whether or not to go beyond the boundaries, what is going beyond the boundaries, is if you drag the entire container and if you have reached the boundaries of the container, you can set it by setting the function  
  12.         if (!m_bBounceable)  
  13.         {  
  14.             const CCPoint minOffset = this->minContainerOffset();  
  15.             const CCPoint maxOffset = this->maxContainerOffset();  
  16.               
  17.             offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x));  
  18.             offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y));  
  19.   
  20.               
  21.         }  
  22.         //CCLOG("The offset x is %f , y is %f",offset.x,offset.y);  
  23.         m_pContainer->setPosition(offset);  
  24.   
  25.         //Great delegate is coming. When you want to do something else besides scrolling the basic interface during the scrolling process, implement the delegate~perfect abstract design depending on your own situation.  
  26.         if (m_pDelegate != NULL)  
  27.         {  
  28.             m_pDelegate->scrollViewDidScroll(this);     
  29.         }  
  30.     }  
  31. }  

An important function, setZoomScale, was called when touching with two points.

setZoomScale

  1. void CCScrollView::setZoomScale(float s)  
  2. {  
  3.     if (m_pContainer->getScale() != s)  
  4.     {  
  5.         CCPoint oldCenter, newCenter;  
  6.         CCPoint center;  
  7.         //Set zoom Center  
  8.         if (m_fTouchLength == 0.0f)   
  9.         {  
  10.             center = ccp(m_tViewSize.width*0.5f, m_tViewSize.height*0.5f);  
  11.             center = this->convertToWorldSpace(center);  
  12.         }  
  13.         else  
  14.         {  
  15.             center = m_tTouchPoint;  
  16.         }  
  17.         //The zoomed-in position of the center results in offset s relative to the world coordinate system, which are calculated here  
  18.         oldCenter = m_pContainer->convertToNodeSpace(center);  
  19.         m_pContainer->setScale(MAX(m_fMinScale, MIN(m_fMaxScale, s)));  
  20.         newCenter = m_pContainer->convertToWorldSpace(oldCenter);  
  21.           
  22.         const CCPoint offset = ccpSub(center, newCenter);  
  23.         //Another appearance of delegate  
  24.         if (m_pDelegate != NULL)  
  25.         {  
  26.             m_pDelegate->scrollViewDidZoom(this);  
  27.         }  
  28.         //The offset that will be generated will be processed  
  29.         this->setContentOffset(ccpAdd(m_pContainer->getPosition(),offset));  
  30.     }  
  31. }  

3.3 ccTouchEnded

If you can persist in seeing this, you can already see the dawn of victory.
  1. void CCScrollView::ccTouchEnded(CCTouch* touch, CCEvent* event)  
  2. {  
  3.     if (!this->isVisible())  
  4.     {  
  5.         return;  
  6.     }  
  7.     //Remove touch from pTouches  
  8.     if (m_pTouches->containsObject(touch))  
  9.     {  
  10.         //When there is one touch left, the method deaccelerateScrolling needs to be called at each frame  
  11.         if (m_pTouches->count() == 1 && m_bTouchMoved)  
  12.         {  
  13.             this->schedule(schedule_selector(CCScrollView::deaccelerateScrolling));  
  14.         }  
  15.         m_pTouches->removeObject(touch);  
  16.         //CCLOG("CCScrollView::ccTouchEnded %d", m_pTouches->count());  
  17.         //m_pDelegate->scrollViewDidStop(this);  
  18.     }   
  19.     //When there is no touch, the state needs to be set  
  20.     if (m_pTouches->count() == 0)  
  21.     {  
  22.         m_bDragging = false;      
  23.         m_bTouchMoved = false;  
  24.     }  
  25. }  

deaccelerateScrolling

In this function, there's a place where you don't understand it and ask the God to point you out

  1. void CCScrollView::deaccelerateScrolling(float dt)  
  2. {  
  3.     //Cancel and return if just before the start of the frame another touch point has started, causing a scrolling state  
  4.     if (m_bDragging)  
  5.     {  
  6.         this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));  
  7.         return;  
  8.     }  
  9.       
  10.     //Fun stuff coming  
  11.       
  12.     float newX, newY;  
  13.     CCPoint maxInset, minInset;  
  14.     CCLOG("The end distance is %f",m_tScrollDistance.x);  
  15.     //I don't know what to do here. I found this offset in the move with output. I don't know why to set it and ask God to answer.  
  16.     m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));  
  17.       
  18.     //Whether to allow out-of-bounds inset information  
  19.     if (m_bBounceable)  
  20.     {  
  21.         maxInset = m_fMaxInset;  
  22.         minInset = m_fMinInset;  
  23.     }  
  24.     else  
  25.     {  
  26.         maxInset = this->maxContainerOffset();  
  27.         minInset = this->minContainerOffset();  
  28.     }  
  29.       
  30.     //check to see if offset lies within the inset bounds  
  31.     newX     = MIN(m_pContainer->getPosition().x, maxInset.x);  
  32.     newX     = MAX(newX, minInset.x);  
  33.     newY     = MIN(m_pContainer->getPosition().y, maxInset.y);  
  34.     newY     = MAX(newY, minInset.y);  
  35.       
  36.     newX = m_pContainer->getPosition().x;  
  37.     newY = m_pContainer->getPosition().y;  
  38.       
  39.     m_tScrollDistance     = ccpSub(m_tScrollDistance, ccp(newX - m_pContainer->getPosition().x, newY - m_pContainer->getPosition().y));  
  40.     m_tScrollDistance     = ccpMult(m_tScrollDistance, SCROLL_DEACCEL_RATE);  
  41.     this->setContentOffset(ccp(newX,newY));  
  42.       
  43.     if ((fabsf(m_tScrollDistance.x) <= SCROLL_DEACCEL_DIST &&  
  44.          fabsf(m_tScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||  
  45.         newY > maxInset.y || newY < minInset.y ||  
  46.         newX > maxInset.x || newX < minInset.x ||  
  47.         newX == maxInset.x || newX == minInset.x ||  
  48.         newY == maxInset.y || newY == minInset.y)  
  49.     {  
  50.         this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));  
  51.         //Cross-border animation, moving slowly from the cross-border part to the function in the non-cross-border state.  
  52.         this->relocateContainer(true);  
  53.         //Great delegate.  
  54.         m_pDelegate->scrollViewDidStop(this);  
  55.           
  56.           
  57.           
  58.     }  
  59. }  


relocateContainer


  1. void CCScrollView::relocateContainer(bool animated)  
  2. {  
  3.     //This function animates the container from its current location to the allowable offset set by the player himself  
  4.     CCPoint oldPoint, min, max;  
  5.     float newX, newY;  
  6.     //Offset value can be set by itself  
  7.     min = this->minContainerOffset();  
  8.     max = this->maxContainerOffset();  
  9.       
  10.     oldPoint = m_pContainer->getPosition();  
  11.   
  12.     newX     = oldPoint.x;  
  13.     newY     = oldPoint.y;  
  14.     if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal)  
  15.     {  
  16.         newX     = MAX(newX, min.x);  
  17.         newX     = MIN(newX, max.x);  
  18.     }  
  19.   
  20.     if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionVertical)  
  21.     {  
  22.         newY     = MIN(newY, max.y);  
  23.         newY     = MAX(newY, min.y);  
  24.     }  
  25.     //Call setContentOffset again, but animation is required  
  26.     if (newY != oldPoint.y || newX != oldPoint.x)  
  27.     {  
  28.         this->setContentOffset(ccp(newX, newY), animated);  
  29.     }  
  30. }  

setContentOffsetInDuration

  1. void CCScrollView::setContentOffsetInDuration(CCPoint offset, float dt)  
  2. {  
  3.     CCFiniteTimeAction *scroll, *expire;  
  4.     //Scrolling skew drawing  
  5.     scroll = CCMoveTo::create(dt, offset);  
  6.     //Animation after scrolling (responsible for stopping performedAnimatedScroll and calling delegate)  
  7.     expire = CCCallFuncN::create(this, callfuncN_selector(CCScrollView::stoppedAnimatedScroll));  
  8.     m_pContainer->runAction(CCSequence::create(scroll, expire, NULL));  
  9.     //Responsible for non-stop delegate calls  
  10.     this->schedule(schedule_selector(CCScrollView::performedAnimatedScroll));  
  11. }  


4. Summary

Seeing this, the CCScrollView has basically finished what belongs to its own unique part.You can see that:
1.CCScrollView supports two operations, scrolling and zooming.
2.CCScrollView decouples data from the interface through delegate.
3.CCScrollView is essentially a CClayer, which displays its own internal container, and the touching and display of CCScrollView is based on the SIze that ViewSize is not.

Topics: Google Attribute