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, 猛戳这里!!!

转载请注明出处!

时间: 2024-08-07 20:54:44

cocos2dx 实现不一样的ScrollView的相关文章

Lua中调用 cocos2d-x 的滑动条/滚动条 ScrollView

 ScrollView 我想玩儿过手机的朋友对滑动条都不陌生吧,(旁边: 这不是废话么???? )   那好吧,废话不多说直接开始ScrollView吧 local m_BaseNode  -- 主场景 local CreateScroll    -- 房间分级滑动视图 local CreateStageNode   -- 创建节点 local m_ScrollView              -- 滑动层变量 local m_Inner     -- 内容器 local addScrol

从零开始のcocos2dx生活(十)ScrollView

目录 简介 基础变量 ScrollViewDelegate Direction _dragging _container _touchMoved _bounceable _touchLength 方法 create setContentSize deaccelerateScrolling maxContainerOffset 和 minContainerOffset 触摸的各阶段 onTouchBegan onTouchMoved onTouchEnded 简介 scrollView是在一定可视

Cocos2dx3.2编写常用UI组件(五)带滚动的表格GridView

前言: 按照惯例先发上效果图: 正文: 先来吐槽几句,一说起滚动效果大家可能会联想到Cocos2dx给我们提供的ScrollView.我一开始也是打算用ScrollView来实现的,但是用着用着发现出现了各种莫名其妙的错误,所以只好自己重新写一个Node,通过onTouchBegan和onTouhMoved两个事件回调来实现滚动的效果. GridView使用说明: 1.利用GridView::create(int row,int column)来创建一个GridView,row和column分别

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

这阶段心绪比較乱,所以这篇开头就不扯淡了.(谁说大姨夫来了我跟谁急!~~)说到大姨夫我突然想到英雄联盟有个美女讲解叫伊芙蕾亚,她的堂弟ID居然叫:姨夫累呀,好笑吧(呵呵,有点冷~~额,我都说不扯淡了).------------前天有个网友问我一些关于scrollView的使用方法,因为在QQ上实在讲不清,所以就利用晚上的时间写这篇博客出来了.本篇要实现的功能是用scrollView 拖动对象时,对象移动到某个固定范围会有放大.缩小的效果.以下開始.在进入正题前我先简短的介绍下scrollView

用C++在cocos2d-x 3.2下完美解决Menu吞掉事件导致ScrollView等无法响应的问题

本文原创,如转载请注明原文地址. http://blog.csdn.net/q229827701/article/details/38901213 最近下了最新版本的cocos2dx 3.2做项目,发现一个坑爹的问题.ScrollView 的子控件上有Menu的时候,ScrollView滑动无法响应. 百度了很多资料,要么说不清楚,要么版本很旧的不适合. 于是自己跑去看了下源码. 发现Menu里面有一句 touchListener->setSwallowTouches(true); 将true修

cocos2dx之TableView和ScrollView的混合使用

************************************************************************** 时间:2015-01-09 作者:Sharing_Li 转载出处:http://blog.csdn.net/sharing_li/article/details/42298317 *************************************************************************** 玩过<开心消消乐>

【转载】Cocos2dx 小技巧(十四)ScrollView实现缩放效果

Cocos2dx 小技巧(十四)ScrollView实现缩放效果 这阶段心绪比较乱,所以这篇开头就不扯淡了.(谁说大姨夫来了我跟谁急!~~)说到大姨夫我突然想到英雄联盟有个美女解说叫伊芙蕾亚,她的堂弟ID竟然叫:姨夫累呀,好笑吧(呵呵,有点冷~~额,我都说不扯淡了). ------------前天有个网友问我一些关于scrollView的用法,由于在QQ上实在讲不清,所以就利用晚上的时间写这篇博客出来了.本篇要实现的功能是用scrollView 拖动对象时,对象移动到某个固定范围会有放大.缩小的

cocos2dx scrollview和controlslider关联

上图演示了要实现的功能 实现步骤: ①算出scrollview真正的contentsize,需要让contentsize.width>=viewsize.width,然后算出v0= viewsize.width - contentsize.width,v1=0 ②设置scrollview的contentoffset.x=v1 ③设置slider最小值为v0,最大值为v1,value为v0 ④拖动scrollview后,设置slider的value为v0+v1-scrollview->getCo

Cocos2d-x 3.2 大富翁游戏项目开发-第五部分 单机游戏-级别选择ScrollView

于MenuScene.cpp 点击单机游戏后会调用 Director::getInstance()->pushScene(MapChooseScene::createScene()); 进入到关卡选择界面,我们採用ScrollView控件制作这个界面 因为scrollview拖动后,位置比較任意,我想是拖动到第二张图片,就完整的显示第二张图片,不要产生偏离位置的现象.所以在移动之后须要进行位置的校正,写了一个adjustScrollView()方法,用来进行该调整. 详细代码例如以下: MapC