cocos2dx 实现不一样的ScrollView

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cocos2dx 实现不一样的ScrollView相关的知识,希望对你有一定的参考价值。

原来在公司被迫加班加点赶工,用lua实现的版本: http://www.cnblogs.com/mmc1206x/p/4146911.html

后来因我个人的需要, 用C++实现了一个版本.

蓦然回首, lua那版不忍直视, 设计拙劣, 代码拙劣, 当然, 这都归咎于那时的我太年轻.

效果图

技术分享

 

ScrollView.h

  1 #pragma once
  2 
  3 #include "Base.h"
  4 
  5 class ScrollView : public ccNode {
  6 public:
  7     struct Param {
  8         float scale;
  9         float width;
 10         float height;
 11         float itemWidth;
 12         float itemHeight;
 13         float anchorY;
 14         std::function<void(ccNode *)> enterHandler;
 15         std::function<void(ccNode *)> leaveHandler;
 16         Param()
 17             : scale(1)
 18             , width(0)
 19             , height(0)
 20             , itemWidth(0)
 21             , itemHeight(0)
 22             , anchorY(0.5f)
 23             , enterHandler([](ccNode *){})
 24             , leaveHandler([](ccNode *){})
 25         {
 26 
 27         }
 28     };
 29 public:
 30     virtual void addChild(ccNode *pNode) override;
 31     virtual void removeChild(ccNode *pNode, bool cleanup = true) override;
 32 
 33     void gotoIndex(int index)
 34     {
 35         _touchIdx = index;
 36         runUpdate();
 37     }
 38 
 39     ccNode *getActiveNode()
 40     {
 41         return getNode(_activeIdx);
 42     }
 43 
 44     ccNode *getTouchNode()
 45     {
 46         return getNode(_touchIdx);
 47     }
 48 
 49     ccNode *getNode(u_int index)
 50     {
 51         auto &childs = getChildren();
 52         return index < (u_int)childs.size()
 53             ? childs.at(index) : nullptr;
 54     }
 55 
 56     float getNodeOffset(u_int index)
 57     {
 58         auto offset = getNodePos(index);
 59         auto max = getRight();
 60         auto min = getLeft();
 61         if (offset <= max && offset >= min)
 62         {
 63             offset -= (max - offset) * (_param.scale - 1);
 64         }
 65         else if (offset < min)
 66         {
 67             offset -= (max - min) * (_param.scale - 1);
 68         }
 69         return offset;
 70     }
 71 private:
 72     bool onTouchBegan(ccTouch *pTouch, ccEvent *pEvent);
 73     void onTouchMoved(ccTouch *pTouch, ccEvent *pEvent);
 74     void onTouchEnded(ccTouch *pTouch, ccEvent *pEvent);
 75     void onTouchCancelled(ccTouch *pTouch, ccEvent *pEvent);
 76     bool moveChilds(float diff);
 77     u_int getNodeIdx(const ccVec2 &worldPoint);
 78     virtual void update(float dt) override;
 79 
 80     void setActive(u_int index)
 81     {
 82         auto pOldNode = getActiveNode();
 83         auto pNewNode = getNode(index);
 84         MMC_ASSERT(pNewNode != nullptr);
 85         if (pOldNode != pNewNode)
 86         {
 87             _activeIdx = index;
 88             if (pOldNode != nullptr)
 89             {
 90                 _param.leaveHandler(pOldNode);
 91             }
 92             _param.enterHandler(pNewNode);
 93         }
 94     }
 95 
 96 private:
 97     float getNodePos(u_int index)
 98     {
 99         return _param.itemWidth * index + _offset;
100     }
101 
102     float getRight()
103     {
104         return _param.itemWidth * _param.scale / 2;
105     }
106 
107     float getLeft()
108     {
109         return getRight() - _param.itemWidth;
110     }
111 
112     bool isActive(u_int index)
113     {
114         auto offset = getNodePos(index);
115         return offset <= getRight() && offset >= getLeft();
116     }
117 
118     void stopUpdate()
119     {
120         unscheduleUpdate();
121     }
122 
123     void runUpdate()
124     {
125         scheduleUpdate();
126     }
127 
128 private:
129     ScrollView();
130     ~ScrollView();
131     void mmcInit(const Param &param);
132 
133     friend ScrollView *utils::createCocos<ScrollView>(const Param &);
134 
135 private:
136     ccEventListenerTouchOneByOne *_listener;
137     Param _param;
138     float _offset;
139     int _activeIdx;
140     int _touchIdx;
141     int _tick;
142 };

 

ScrollView.cpp

  1 #include "ScrollView.h"
  2 
  3 ScrollView::ScrollView()
  4     : _listener(nullptr)
  5     , _activeIdx(INVALID_VALUE)
  6     , _touchIdx(INVALID_VALUE)
  7     , _offset(0)
  8 {
  9 }
 10 
 11 ScrollView::~ScrollView()
 12 {
 13 }
 14 
 15 void ScrollView::mmcInit(const Param &param)
 16 {
 17     if (!Node::init())
 18     {
 19         MMC_ASSERT(false);
 20     }
 21     _listener = ccEventListenerTouchOneByOne::create();
 22     _listener->onTouchBegan = CC_CALLBACK_2(ScrollView::onTouchBegan, this);
 23     _listener->onTouchMoved = CC_CALLBACK_2(ScrollView::onTouchMoved, this);
 24     _listener->onTouchEnded = CC_CALLBACK_2(ScrollView::onTouchEnded, this);
 25     _listener->onTouchCancelled = CC_CALLBACK_2(ScrollView::onTouchCancelled, this);
 26     _listener->setSwallowTouches(true);
 27     getEventDispatcher()->addEventListenerWithSceneGraphpriority(_listener, this);
 28     _offset = 0;
 29     _param = param;
 30     runUpdate();
 31 }
 32 
 33 void ScrollView::addChild(ccNode *pNode)
 34 {
 35     auto childCount = getChildrenCount();
 36     auto offset = getNodeOffset(childCount);
 37     pNode->setPositionX(offset);
 38     pNode->setAnchorPoint(ccVec2(0.5f, _param.anchorY));
 39     if (_activeIdx == INVALID_VALUE && _touchIdx == INVALID_VALUE)
 40     {
 41         _touchIdx = childCount;
 42         runUpdate();
 43     }
 44     Node::addChild(pNode);
 45 }
 46 
 47 void ScrollView::removeChild(Node *pNode, bool cleanup)
 48 {
 49     auto pActive = getActiveNode();
 50     if (pActive == pNode)
 51     {
 52         _activeIdx = INVALID_VALUE;
 53     }
 54     auto pTouch = getTouchNode();
 55     if (pTouch == pNode)
 56     {
 57         _touchIdx = INVALID_VALUE;
 58     }
 59     Node::removeChild(pNode, cleanup);
 60 }
 61 
 62 bool ScrollView::onTouchBegan(ccTouch *pTouch, ccEvent *pEvent)
 63 {
 64     const auto &size = getContentSize();
 65     const auto &touchRect = ccRect(
 66         _param.width * -0.5f, 
 67         _param.height * -_param.anchorY,
 68         _param.width, _param.height);
 69     const auto &worldPoint = pTouch->getLocation();
 70     const auto &localPoint = convertToNodeSpace(worldPoint);
 71     auto isTouch = touchRect.containsPoint(localPoint);
 72     if (isTouch)
 73     {
 74         _tick = clock();
 75         stopUpdate();
 76     }
 77     return isTouch;
 78 }
 79 
 80 void ScrollView::onTouchMoved(ccTouch *pTouch, ccEvent *pEvent)
 81 {
 82     auto diffOffset = pTouch->getDelta().x;
 83     auto nowtick = clock();
 84     auto difftick = nowtick - _tick;
 85 #ifdef WIN32
 86     int offsetIndex = diffOffset / difftick / 2;
 87 #else
 88     int offsetIndex = diffOffset / difftick * 1000 / 2;
 89 #endif
 90     _tick = nowtick;
 91     _touchIdx = _activeIdx - offsetIndex;
 92     if (_touchIdx >= getChildrenCount())
 93     {
 94         _touchIdx = getChildrenCount() - 1;
 95     }
 96     if (_touchIdx < 0)
 97     {
 98         _touchIdx = 0;
 99     }
100     moveChilds(diffOffset);
101 }
102 
103 void ScrollView::onTouchEnded(ccTouch *pTouch, ccEvent *pEvent)
104 {
105     const auto &beg = pTouch->getStartLocation();
106     const auto &end = pTouch->getLocation();
107     const auto &delta = beg.getDistance(end);
108     if (delta < 10)
109     {
110         _touchIdx = getNodeIdx(end);
111     }
112     runUpdate();
113 }
114 
115 void ScrollView::onTouchCancelled(ccTouch *pTouch, ccEvent *pEvent)
116 {
117 
118 }
119 
120 void ScrollView::update(float dt)
121 {
122     auto pMoveNode = getTouchNode();
123     if (pMoveNode == nullptr)
124     {
125         pMoveNode = getActiveNode();
126     }
127 
128     if (pMoveNode != nullptr)
129     {
130         auto nowOffset = pMoveNode->getPositionX();
131         auto newOffset = nowOffset * 0.1f;
132         if (!moveChilds(-newOffset))
133         {
134             pMoveNode = nullptr;
135         }
136     }
137 
138     if (pMoveNode == nullptr)
139     {
140         stopUpdate();
141     }
142 }
143 
144 bool ScrollView::moveChilds(float diff)
145 {
146     _offset += diff;
147     auto scaleLength = _param.itemWidth * _param.scale / 2;
148     auto &childs = getChildren();
149     for (auto i = 0; i != childs.size(); ++i)
150     {
151         auto pChild = childs.at(i);
152         auto offset = getNodeOffset(i);
153         if (std::abs(offset) * 2 > _param.width)
154         {
155             pChild->setVisible(false);
156         }
157         else
158         {
159             auto newScale = (1 - std::abs(offset) / getRight()) * _param.scale;
160             if (newScale < 1) newScale = 1;
161             if (newScale > _param.scale) newScale = _param.scale;
162             pChild->setScale(newScale);
163             pChild->setVisible(true);
164             pChild->setPositionX(offset);
165             if (isActive(i))
166             {
167                 setActive(i);
168             }
169         }
170     }
171     return std::abs(diff) >= 0.1f;
172 }
173 
174 u_int ScrollView::getNodeIdx(const ccVec2 &worldPoint)
175 {
176     const auto &localPoint = convertToNodeSpace(worldPoint);
177     const auto &childs = getChildren();
178     ccRect rect;
179     for (auto i = 0; i != childs.size(); ++i)
180     {
181         rect.origin.x = getNodeOffset(i) - _param.itemWidth / 2;
182         rect.origin.y = -_param.anchorY * _param.itemHeight;
183         rect.size.width = _param.itemWidth;
184         rect.size.height = _param.itemHeight;
185         if (rect.containsPoint(localPoint))
186         {
187             return i;
188         }
189     }
190     return INVALID_VALUE;
191 }

这么简洁且通俗易懂的代码, 我想就不用填注释了吧.

 

这个版本的实现思路要比原来的更简洁.

去掉了所谓的惯性控制对象

去掉了N多变量,

优化了一些接口.

 

原来,

快速滑动会激发惯性, 当惯性停止时, 再调整位置, 这里用到2个定时器, 一个用于控制惯性, 一个用于调整位置;

单击锁定放在子节点的Touch里响应, 因此要注册很多EventListener;

没有直接锁定功能, 貌似因为设计的问题, 无法加上这个功能, 年代久远, 懒得考察;

以及一堆让现在的我无法忍受的代码.

 

现在,

取消单独定时器, 所有计时操作都在Node::update里处理, 节能减排;

单击锁定由一个EventListener处理, 节能减排;

可以直接锁定某个子节点;

 

两者最大的区别应该在于惯性的处理,

老板的根据滑动速度, 计算惯性速度, 然后等待惯性消失, 之后再调整位置, 这个过程很**.

新版的处理很巧妙, 通过滑动速度, 计算出将被锁定的节点, 之后直接锁定, 对接口的重用要好很多, 并且思路, 实现都很简洁.

 

下载 DEMO, 猛戳这里!!!

 

转载请注明出处!

以上是关于cocos2dx 实现不一样的ScrollView的主要内容,如果未能解决你的问题,请参考以下文章

Cocos2dx 小技巧(十四)ScrollView实现缩放效果

cocos2dx中的ScrollView

从零开始のcocos2dx生活ScrollView

cocos2dx做的游戏帧数很低,请问为啥

Cocos2dx CCRendertexture 和 framebuffer 对象问题。我的新纹理看起来不一样。 Alpha 值不正确

ScrollView 实现子视图滑动到顶部时固定不动