Cocos2d-x 学习笔记(21) ScrollView (CCScrollView)
1. 简介
CCScrollView.cpp文件内的滚动视图ScrollView直接继承了Layer+ActionTweenDelegate。
滚动视图能在屏幕区域内,用户通过触摸拖动屏幕,实现大于屏幕尺寸的图片的滚动效果。
滚动视图尺寸是我们的可视尺寸,滚动视图包含的成员container(layer)是被拖动的大图所在的层。
实现滚动视图效果,需要以下几个方面的工作:
· 获取屏幕尺寸。
· 一个layer。
· layer中addChild一张/多张大图。
· layer尺寸设置成包含所有图片的尺寸。
· 创建滚动视图,需要可视区域size和layer。
· this->addChild(滚动视图)。
2. 一些变量的作用
- ScrollViewDelegate* _delegate
ScrollView使用了Delegate机制,ScrollView关联了一个delegate。ScrollViewDelegate类有两个虚函数,我们可以用子类继承该类,重写两方法。
virtual void scrollViewDidScroll(ScrollView* view) {}; //滚动时触发的回调函数
virtual void scrollViewDidZoom(ScrollView* view) {}; //缩放时触发的回调函数
- Direction _direction
用枚举表示限制的滚动方向。
enum class Direction
{
NONE = -,
HORIZONTAL = ,
VERTICAL,
BOTH
}
- bool _dragging
用户是否开始拖动的标志。仅在onTouchBegan置true,表明拖动开始;构造函数、onTouchEnded、onTouchCancelled中置false,表明没有拖动。
- bool _touchMoved
用户是否已经拖动的标志。和_dragging类似,但仅在onTouchMoved的第一次执行中置true,在onTouchEnded、onTouchCancelled中置false。
- bool _bounceable
回弹功能的标志,在拖动中和结束时,即onTouchMoved、onTouchEnd方法,通过该标志决定了是否有回弹效果。
create方法置true,即默认开启回弹。
- Vec2 _maxInset
- Vec2 _minInset
默认均为0,是container偏移量范围的两个临界点,仅通过setContentSize设置,仅对有回弹的ScrollView起作用。
3. 一些方法的使用
- create
两种重载:
ScrollView* create(Size size, Node* container = NULL)
ScrollView* create()
create方法调用了initWithViewSize方法。
container位置设为(0,0)。
Layer在构造函数中,忽略锚点对位置的影响,位置基准点始终为左下角,位置设为(0,0),锚点(0.5,0.5)。ScrollView继承了Layer,container一般是Layer类型。
- setContainer
设置成员container:
void ScrollView::setContainer(Node * pContainer)
{
if (nullptr == pContainer)
return; this->removeAllChildrenWithCleanup(true); //删除原先的container
this->_container = pContainer; this->_container->setIgnoreAnchorPointForPosition(false); //container的位置需要锚点
this->_container->setAnchorPoint(Vec2(0.0f, 0.0f)); this->addChild(this->_container); //不是Node的addChild this->setViewSize(this->_viewSize);
}
值得注意的是:
如果layer是作为create的参数而设为container的话,layer锚点(0.5,0.5),忽略锚点对于位置的影响。
如果layer是通过setContainer设为container的话,layer锚点(0,0),不忽略锚点对位置的影响。
虽然上面两种情况锚点不同,但是最终都是以左下角为基准点设置位置的。
这里的addChild方法不是父类Node的方法,而是ScrollView重写的addChild。
- addChild
ScrollView重写了Node的addChild方法。
void addChild(Node * child, int zOrder, int tag)
void addChild(Node * child, int zOrder, const std::string &name)
滚动视图进行addChild,实际上是把参数node添加到滚动视图的成员container里。滚动视图的removeChild同理。
如果node是container,则把container作为ScrollView的子节点。
void ScrollView::addChild(Node * child, int zOrder, int tag)
{
if (_container != child) {
_container->addChild(child, zOrder, tag);
} else {
Layer::addChild(child, zOrder, tag);
}
}
- setViewSize
设置可视区域尺寸。先把参数作为viewSize值。接着执行Layer::setContentSize(size),设置ScrollView的尺寸。
- setContentSize
重写了Node的该方法,设置的是container的尺寸。
调用了updateInset方法,执行:
_maxInset = this->maxContainerOffset();
_maxInset.set(_maxInset.x + _viewSize.width * INSET_RATIO, _maxInset.y + _viewSize.height * INSET_RATIO);
_minInset = this->minContainerOffset();
_minInset.set(_minInset.x - _viewSize.width * INSET_RATIO, _minInset.y - _viewSize.height * INSET_RATIO);
设置了_maxInset:maxContainerOffset的值加上部分可视范围,_minInset:minContainerOffset减去上部分可视范围。
_maxInset和_minInset在有回弹时才有用处。
- setTouchEnabled(bool enabled)
初始化监听器或删除监听器。
void ScrollView::setTouchEnabled(bool enabled)
{
//先删除原监听器
_eventDispatcher->removeEventListener(_touchListener);
_touchListener = nullptr; if (enabled) //如果启用触摸监听
{
_touchListener = EventListenerTouchOneByOne::create();
_touchListener->setSwallowTouches(true); //... ScrollView的4个回调函数作为监听器4种触摸的回调函数 _eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this);
}
else // 如果停用触摸监听
{
_dragging = false;
_touchMoved = false;
_touches.clear();
}
}
- maxContainerOffset
- minContainerOffset
计算container允许的偏移量范围。
值为正(负),即container在正(负)方向被拖动的最大值。
计算时,需要“anchorPoint”,此点计算过程:
Point anchorPoint = _container->isIgnoreAnchorPointForPosition()?Point::ZERO:_container->getAnchorPoint();
我们知道,container是可视区域ScrollView的子节点,container位置的基准点都是自己的左下角,从而使得container的左下边与可视区域的左下边对齐,container才能在拖动过程中被完整展示。
可以看出,在不人为修改container锚点的情况下,无论是create方法设置container,还是setContainer方法,anchorPoint将始终为(0,0)。
因此,maxContainerOffset的返回值永远为(0,0)。minContainerOffset的返回值的X永远是container宽减去可视区域宽,Y永远是container长减去可视区域长。
4. Touch事件的4个回调函数
ScrollView继承了Layer,因此可以重写Layer的触摸事件的4个回调函数,实现对触摸拖动的处理。
- onTouchBegan
ScrollView的触摸支持单点/多点触摸
bool ScrollView::onTouchBegan(Touch* touch, Event* /*event*/)
{
if (!this->isVisible() || !this->hasVisibleParents())
{
return false;
} Rect frame = getViewRect(); //当前可视范围的Rect //要求:触摸发生在单点或两点,触摸不是移动状态,触摸点在可视范围内
if (_touches.size() > ||
_touchMoved ||
!frame.containsPoint(touch->getLocation()))
{
return false;
} if (std::find(_touches.begin(), _touches.end(), touch) == _touches.end())
{
_touches.push_back(touch); //容器内的Touch数量1或2
} if (_touches.size() == ) //单点触摸,用于拖动
{
_touchPoint = this->convertTouchToNodeSpace(touch); //相对于ScrollView的坐标
_touchMoved = false; //正在移动的标志
_dragging = true; //拖动专用标志
_scrollDistance.setZero();
_touchLength = 0.0f;
}
else if (_touches.size() == ) //两点触摸,用于缩放
{
_touchPoint = (this->convertTouchToNodeSpace(_touches[]).getMidpoint(
this->convertTouchToNodeSpace(_touches[]))); // 两点的中点,作为缩放的“中心” _touchLength = _container->convertTouchToNodeSpace(_touches[]).getDistance(
_container->convertTouchToNodeSpace(_touches[])); //两点的距离,用于计算Moved时缩放大小 _dragging = false; //拖动专用标志
}
return true;
}
- onTouchMoved
在该方法内部,触摸分为单点拖动和两点缩放分别进行处理。
-- 拖动
当前触摸是拖动情况的条件是:
_touches.size() == && _dragging
单点拖动时的基本逻辑是:
根据限制方向的不同,计算container移动的向量,设置container的新位置。当有回弹功能时,且新位置超出偏移范围,移动向量需要缩小,这是回弹效果之一。当没有回弹功能时,且新位置超出偏移范围,则位置被置于边界。
流程大致是:
1. 当前坐标减去Began坐标或上次Moved的坐标(_touchPoint),得出坐标移动的向量moveDistance。
2. 根据限制的拖动方向(_direction)的不同分3种情况。计算该情况下的移动长度(float dis)。
3. 判断此时container位置是否超出偏移范围。如果超出,则对向量moveDistance缩小(乘回弹系数BOUNCE_BACK_FACTOR)。
4. 如果当前是Began触摸后的第一次Moved(bool _touchMoved),且本次移动长度dis小于最小移动距离时MOVE_INCH,回调函数结束,即忽略本次拖动。
5. 如果当前是Began触摸后的第一次Moved(bool _touchMoved),本次移动向量moveDistance改为0。此处不知为何?
6. 本次Moved坐标赋给_touchPoint,供下次Moved使用。
7. _touchMoved置true。
8. 根据限制的拖动方向(_direction)的不同分3种情况。计算该情况下的移动向量moveDistance。
9. 计算container新坐标。当前坐标加移动向量moveDistance。
10. 根据是否回弹分两种情况,设置container新坐标(setContentOffset方法)。
-- 缩放
当前触摸是缩放情况的条件是:
_touches.size() == && !_dragging
两点缩放时的基本逻辑是:
根据当前两点距离和开始距离的比值,计算出当前的缩放值。两点中心在缩放前后的位置变化作为偏移量,设置container新的位置。实现了缩放是围绕两点中心进行,而不是锚点的效果。
流程大致是:
1. 获取当前两个touch的距离长度。
2. 执行setZoomScale方法,参数为:当前缩放值=开始时缩放值scale*(当前两点距离/开始时两点距离)
this->setZoomScale(this->getZoomScale()*len/_touchLength);
接下来看setZoomScale方法的分析:
1. 判断开始时两点距离是否为0。为0时,可视区域的中心点作为点center。不为0时,_touchPoint即开始时两点连线的中点作为点center。
2. 点center坐标转为相对container的坐标。
oldCenter = _container->convertToNodeSpace(center);
3. container执行setScale方法,参数为我们计算的当前缩放值。
缩放值范围会被调整为_minScale和_maxScale之间,但这两个范围值在init方法中默认都是1,故需要我们自行更改范围才能实现缩放。两者都有set方法。
4. setScale之后,container的位置坐标会更改。把缩放后的点center坐标转为相对container的世界坐标。
newCenter = _container->convertToWorldSpace(oldCenter);
5. 计算缩放前后点center坐标的差值,作为偏移量offset。
6. 执行缩放触发的scrollViewDidZoom方法。
7. 执行setContentOffset方法,参数为container当前坐标加上偏移量offset。设置了本次缩放后的位置。
- onTouchEnded
onTouchEnded基本逻辑是:
当结束触摸时:如果container位置在偏移范围内,直接设置container位置为当前结束点的位置;如果container位置超出偏移范围,根据有无回弹效果分为下面两种情况。
有回弹效果时:创建动作序列,对container执行runAction。第一个动作是MoveTo,设置了时间、位置是边界坐标。第二个动作是回调执行scrollViewDidScroll。
无回弹效果时:Moved和Ended时container位置被限制在偏移范围内。
该方法中把拖动标志_dragging和_touchMoved置false。
5. scrollViewDidScroll scrollViewDidZoom 相关
ScrollView有成员ScrollViewDelegate* _delegate,我们可以用子类重写ScrollViewDelegate的拖动和缩放触发的函数。
- scrollViewDidScroll
该方法在3个函数里被调用,setContentOffset、performedAnimatedScroll、stoppedAnimatedScroll。
setContentOffset:用于设置container的位置。当立即设置位置,将会触发scrollViewDidScroll;当有回弹故用动作设置位置时,不会在本函数中触发函数scrollViewDidScroll。
performedAnimatedScroll:是一个Timer的回调函数。当有回弹效果,在动作序列中MoveTo执行时,每帧触发该回调函数,回调函数中触发的是scrollViewDidScroll。
stoppedAnimatedScroll:回弹效果的动作序列MoveTo结束后,执行该函数,销毁包含performedAnimatedScroll的Timer,并执行performedAnimatedScroll。
所以,对于拖动的情况,当触摸在移动,每次调用onTouchMoved都会触发scrollViewDidScroll。回弹时每帧触发scrollViewDidScroll。
- scrollViewDidZoom
在每次缩放触发onTouchMoved函数时,其中的setZoomScale方法都会触发scrollViewDidZoom。setZoomScale中还需要根据偏移量设置container新位置,调用的setContentOffset会调用scrollViewDidScroll。
所以,对于缩放的情况,当触摸在移动,每次调用onTouchMoved会触发scrollViewDidZoom和scrollViewDidScroll。
Cocos2d-x 学习笔记(21) ScrollView (CCScrollView)的更多相关文章
- Ext.Net学习笔记21:Ext.Net FormPanel 字段验证(validation)
Ext.Net学习笔记21:Ext.Net FormPanel 字段验证(validation) 作为表单,字段验证当然是不能少的,今天我们来一起看看Ext.Net FormPanel的字段验证功能. ...
- SQL反模式学习笔记21 SQL注入
目标:编写SQL动态查询,防止SQL注入 通常所说的“SQL动态查询”是指将程序中的变量和基本SQL语句拼接成一个完整的查询语句. 反模式:将未经验证的输入作为代码执行 当向SQL查询的字符串中插入别 ...
- ArcGIS API for JavaScript 4.2学习笔记[21] 对3D场景上的3D要素进行点击查询【Query类学习】
有人问我怎么这个系列没有写自己做的东西呢? 大哥大姐,这是"学习笔记"啊!当然主要以解读和笔记为主咯. 也有人找我要实例代码(不是示例),我表示AJS尚未成熟,现在数据编辑功能才简 ...
- [原创]java WEB学习笔记21:MVC案例完整实践(part 2)---DAO层设计
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- Cocos2d-x 学习笔记(21.1) ScrollView “甩出”效果与 deaccelerateScrolling 方法
1. 简介 “甩出”效果是当我们快速拖动container并松开后,container继续朝原方向运动,但是渐渐减速直到停止的效果. ScrollView的onTouchEnded方法会设置Timer ...
- Linux下汇编语言学习笔记21 ---
这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...
- Linux学习笔记(21) Linux日志管理
1. 简介 (1) 日志服务 在CentOS 6.x中日志服务已经由rsyslogd取代了原先的syslogd服务.rsyslogd日志服务更加先进,功能更多.但是不论该服务的使用,还是日志文件的格式 ...
- struts2视频学习笔记 21(输入校验的流程)
课时21 输入校验的流程 1.类型转换器对请求参数执行类型转换,并把转换后的值赋给action中的属性. 2.如果在执行类型转换的过程中出现异常,系统会将异常信息保存到ActionContext,co ...
- android学习笔记11——ScrollView
ScrollView——滚动条 用于内容显示不全,可提供滚动条下来形式,显示其余内容. ScrollView和HorizontalScrollView是为控件或者布局添加滚动条 特点如下: 1.只能有 ...
随机推荐
- pgsql查询优化之模糊查询
前言 一直以来,对于搜索时模糊匹配的优化一直是个让人头疼的问题,好在强大pgsql提供了优化方案,下面就来简单谈一谈如何通过索引来优化模糊匹配 案例 我们有一张千万级数据的检查报告表,需要通过检查报告 ...
- [JavaWeb] Ubuntu下载eclipse for ee
进入网站进行下载 https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2019- ...
- 洛谷P2172 [国家集训队]部落战争 题解
题目链接:https://www.luogu.org/problemnew/show/P2172 分析: 不要被[国家集训队]的标签吓到,其实这题不是很难. 本题可以对比P4304 [TJOI2013 ...
- ES6 symbol 以及symbol的简单应用
前置 1.ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值. 2.Symbol 值通过Symbol函数生成. 3.Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实 ...
- 快速掌握mongoDB(五)——读写分离的副本集实现和Sharing介绍
1 mongoDB副本集 1 副本集简介 前边我们介绍都是单机MongoDB的使用,在实际开发中很少会用单机MongoDB,因为使用单机会有数据丢失的风险,同时单台服务器无法做到高可用性(即当服务器宕 ...
- Redis 学习笔记(篇六):数据库
Redis 是一个使用 C 语言编写的 NoSql 的数据库,本篇就讲解在 Redis 中数据库是如何存储的?以及和数据库有关的一些操作. Redis 中的所有数据库都保存在 redis.h/redi ...
- linux初学者-数据库管理MariaDB篇
linux初学者-数据库管理MariaDB篇 MariaDB是一种数据库管理系统,是MySQL的一个分支,但是比MySQL更加优秀,可以说是MySQL的替代品.MariaDB使用的是SQL语句.下文将 ...
- Http接口调用示例教程
介绍HttpClient库的使用前,先介绍jdk里HttpURLConnection,因为HttpClient是开源的第三方库,使用方便,不过jdk里的都是比较基本的,有时候没有HttpClient的 ...
- 【iOS】Signing for "project_name" requires a development team. Select a development team in the project editor
Xcode 8.3.2 运行 GitHub 上下载的代码时报了这个错. 解决方法: 单击工程名 --> Signing --> Team --> 选择对应的Account(如果没有A ...
- 基于zookeeper集群的云平台-配置中心的功能设计
最近准备找工作面试,就研究了下基于zookeeper集群的配置中心. 下面是自己设想的关于开源的基于zookeeper集群的云平台-配置中心的功能设计.大家觉得哪里有问题,请提出宝贵的意见和建议,谢谢 ...