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 ¶m); 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 ¶m) 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处理, 节能减排;
可以直接锁定某个子节点;
两者最大的区别应该在于惯性的处理,
老板的根据滑动速度, 计算惯性速度, 然后等待惯性消失, 之后再调整位置, 这个过程很**.
新版的处理很巧妙, 通过滑动速度, 计算出将被锁定的节点, 之后直接锁定, 对接口的重用要好很多, 并且思路, 实现都很简洁.
转载请注明出处!
以上是关于cocos2dx 实现不一样的ScrollView的主要内容,如果未能解决你的问题,请参考以下文章
Cocos2dx 小技巧(十四)ScrollView实现缩放效果
Cocos2dx CCRendertexture 和 framebuffer 对象问题。我的新纹理看起来不一样。 Alpha 值不正确