/**************************************************************************** Copyright (c) 2012 cocos2d-x.org Copyright (c) 2010 Sangwoo Im http://www.cocos2d-x.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "CCScrollViewSmooth.h" #include "platform/CCDevice.h" #include "2d/CCActionInstant.h" #include "2d/CCActionInterval.h" #include "2d/CCActionTween.h" #include "base/CCDirector.h" #include "base/CCEventDispatcher.h" #include "renderer/CCRenderer.h" #include "base/ccUtils.h" #include "2d/CCTweenFunction.h" #include NS_CC_EXT_BEGIN #define SCROLL_DEACCEL_RATE 0.95f #define SCROLL_DEACCEL_DIST 1.0f #define BOUNCE_DURATION 0.15f #define INSET_RATIO 0.2f #define MOVE_INCH 7.0f/160.0f #define BOUNCE_BACK_FACTOR 0.35f //zg #define TURN_PAGE_SPEED 0.20f //designPixl/ms #define INVALID_PAGE 0xfff #define TURN_PAGE_MIN_OFFSET_RATIO 0.1f static float convertDistanceFromPointToInch(float pointDis) { auto glview = Director::getInstance()->getOpenGLView(); float factor = ( glview->getScaleX() + glview->getScaleY() ) / 2; return pointDis * factor / Device::getDPI(); } ScrollViewSmooth::ScrollViewSmooth() : _delegate(nullptr) , _direction(Direction::BOTH) , _dragging(false) , _container(nullptr) , _touchMoved(false) , _bounceable(false) , _clippingToBounds(false) , _touchLength(0.0f) , _minScale(0.0f) , _maxScale(0.0f) , _scissorRestored(false) , _touchListener(nullptr) , _animatedScrollAction(nullptr) ,_autoScrollCurrentlyOutOfBoundary(false) ,_autoScrollBraking(false) // zg , m_bPaged(false) , m_currPage(0) , m_targetPage(INVALID_PAGE) , m_touchBeganTime(0) , m_touchBeganOffset(0) , m_pageAdjustSize(Size::ZERO) , m_bIsScrollingPaused(false) // zml , _isInterceptTouch(false) , _bePressed(false) , _autoScrolling(false) , _autoScrollTotalTime(0) , _autoScrollAccumulatedTime(0) , _touchMovePreviousTimestamp(0) { } ScrollViewSmooth::~ScrollViewSmooth() { } ScrollViewSmooth* ScrollViewSmooth::create(Size size, Node* container/* = nullptr*/) { ScrollViewSmooth* pRet = new (std::nothrow) ScrollViewSmooth(); if (pRet && pRet->initWithViewSize(size, container)) { pRet->autorelease(); } else { CC_SAFE_DELETE(pRet); } return pRet; } ScrollViewSmooth* ScrollViewSmooth::create() { ScrollViewSmooth* pRet = new (std::nothrow) ScrollViewSmooth(); if (pRet && pRet->init()) { pRet->autorelease(); } else { CC_SAFE_DELETE(pRet); } return pRet; } bool ScrollViewSmooth::initWithViewSize(Size size, Node *container/* = nullptr*/) { if (Layer::init()) { _container = container; if (!this->_container) { _container = Layer::create(); _container->setIgnoreAnchorPointForPosition(false); _container->setAnchorPoint(Vec2(0.0f, 0.0f)); } this->setViewSize(size); setTouchEnabled(true); _touches.reserve(EventTouch::MAX_TOUCHES); _delegate = nullptr; _bounceable = true; _clippingToBounds = true; //_container->setContentSize(Size::ZERO); _direction = Direction::BOTH; _container->setPosition(0.0f, 0.0f); _touchLength = 0.0f; this->addChild(_container); _minScale = _maxScale = 1.0f; return true; } return false; } bool ScrollViewSmooth::init() { return this->initWithViewSize(Size(200, 200), nullptr); } bool ScrollViewSmooth::isNodeVisible(Node* node) { const Vec2 offset = this->getContentOffset(); const Size size = this->getViewSize(); const float scale = this->getZoomScale(); Rect viewRect; viewRect = Rect(-offset.x/scale, -offset.y/scale, size.width/scale, size.height/scale); return viewRect.intersectsRect(node->getBoundingBox()); } void ScrollViewSmooth::pause(Ref* /*sender*/) { _container->pause(); auto& children = _container->getChildren(); for(const auto &child : children) { child->pause(); } } void ScrollViewSmooth::resume(Ref* /*sender*/) { auto& children = _container->getChildren(); for(const auto &child : children) { child->resume(); } _container->resume(); } bool ScrollViewSmooth::isTouchEnabled() const { return _touchListener != nullptr; } void ScrollViewSmooth::setTouchEnabled(bool enabled) { _eventDispatcher->removeEventListener(_touchListener); _touchListener = nullptr; if (enabled) { _touchListener = EventListenerTouchOneByOne::create(); _touchListener->setSwallowTouches(true); _touchListener->onTouchBegan = CC_CALLBACK_2(ScrollViewSmooth::onTouchBegan, this); _touchListener->onTouchMoved = CC_CALLBACK_2(ScrollViewSmooth::onTouchMoved, this); _touchListener->onTouchEnded = CC_CALLBACK_2(ScrollViewSmooth::onTouchEnded, this); _touchListener->onTouchCancelled = CC_CALLBACK_2(ScrollViewSmooth::onTouchCancelled, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this); } else { _dragging = false; _touchMoved = false; _touches.clear(); } } void ScrollViewSmooth::setContentOffset(Vec2 offset, bool animated/* = false*/) { if (animated) { //animate scrolling this->setContentOffsetInDuration(offset, BOUNCE_DURATION); } else { //set the container position directly if (!_bounceable) { const Vec2 minOffset = this->minContainerOffset(); const Vec2 maxOffset = this->maxContainerOffset(); offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x)); offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y)); } _container->setPosition(offset); if (_delegate != nullptr) { _delegate->scrollViewDidScroll(this); } } } void ScrollViewSmooth::setContentOffsetInDuration(Vec2 offset, float dt) { FiniteTimeAction *scroll, *expire; if (_animatedScrollAction) { stopAnimatedContentOffset(); } scroll = MoveTo::create(dt, offset); expire = CallFuncN::create(CC_CALLBACK_1(ScrollViewSmooth::stoppedAnimatedScroll,this)); _animatedScrollAction = _container->runAction(Sequence::create(scroll, expire, nullptr)); _animatedScrollAction->retain(); this->schedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::performedAnimatedScroll)); } void ScrollViewSmooth::stopAnimatedContentOffset() { stopAction(_animatedScrollAction); _animatedScrollAction->release(); _animatedScrollAction = nullptr; stoppedAnimatedScroll(this); } Vec2 ScrollViewSmooth::getContentOffset() { return _container->getPosition(); } void ScrollViewSmooth::setZoomScale(float s) { if (_container->getScale() != s) { Vec2 oldCenter, newCenter; Vec2 center; if (_touchLength == 0.0f) { center.set(_viewSize.width*0.5f, _viewSize.height*0.5f); center = this->convertToWorldSpace(center); } else { center = _touchPoint; } oldCenter = _container->convertToNodeSpace(center); _container->setScale(MAX(_minScale, MIN(_maxScale, s))); newCenter = _container->convertToWorldSpace(oldCenter); const Vec2 offset = center - newCenter; if (_delegate != nullptr) { _delegate->scrollViewDidZoom(this); } this->setContentOffset(_container->getPosition() + offset); } } float ScrollViewSmooth::getZoomScale() { return _container->getScale(); } void ScrollViewSmooth::setZoomScale(float s, bool animated) { if (animated) { this->setZoomScaleInDuration(s, BOUNCE_DURATION); } else { this->setZoomScale(s); } } void ScrollViewSmooth::setZoomScaleInDuration(float s, float dt) { if (dt > 0) { if (_container->getScale() != s) { ActionTween *scaleAction; scaleAction = ActionTween::create(dt, "zoomScale", _container->getScale(), s); this->runAction(scaleAction); } } else { this->setZoomScale(s); } } void ScrollViewSmooth::updateTweenAction(float value, const std::string& /*key*/) { this->setZoomScale(value); } void ScrollViewSmooth::setViewSize(Size size) { _viewSize = size; Layer::setContentSize(size); } Node * ScrollViewSmooth::getContainer() { return this->_container; } void ScrollViewSmooth::setContainer(Node * pContainer) { // Make sure that '_container' has a non-nullptr value since there are // lots of logic that use '_container'. if (nullptr == pContainer) return; this->removeAllChildrenWithCleanup(true); this->_container = pContainer; this->_container->setIgnoreAnchorPointForPosition(false); this->_container->setAnchorPoint(Vec2(0.0f, 0.0f)); this->addChild(this->_container); this->setViewSize(this->_viewSize); } bool ScrollViewSmooth::hasVisibleParents() const { auto parent = this->getParent(); for( auto c = parent; c != nullptr; c = c->getParent() ) { if( !c->isVisible() ) { return false; } } return true; } void ScrollViewSmooth::relocateContainer(bool animated) { Vec2 oldPoint, min, max; float newX, newY; min = this->minContainerOffset(); max = this->maxContainerOffset(); oldPoint = _container->getPosition(); newX = oldPoint.x; newY = oldPoint.y; if (_direction == Direction::BOTH || _direction == Direction::HORIZONTAL) { newX = MAX(newX, min.x); newX = MIN(newX, max.x); } if (_direction == Direction::BOTH || _direction == Direction::VERTICAL) { newY = MIN(newY, max.y); newY = MAX(newY, min.y); } if (newY != oldPoint.y || newX != oldPoint.x) { this->setContentOffset(Vec2(newX, newY), animated); } } Vec2 ScrollViewSmooth::maxContainerOffset() { Point anchorPoint = _container->isIgnoreAnchorPointForPosition()?Point::ZERO:_container->getAnchorPoint(); float contW = _container->getContentSize().width * _container->getScaleX(); float contH = _container->getContentSize().height * _container->getScaleY(); return Vec2(anchorPoint.x * contW, anchorPoint.y * contH); } Vec2 ScrollViewSmooth::minContainerOffset() { Point anchorPoint = _container->isIgnoreAnchorPointForPosition()?Point::ZERO:_container->getAnchorPoint(); float contW = _container->getContentSize().width * _container->getScaleX(); float contH = _container->getContentSize().height * _container->getScaleY(); return Vec2(_viewSize.width - (1 - anchorPoint.x) * contW, _viewSize.height - (1 - anchorPoint.y) * contH); } void ScrollViewSmooth::deaccelerateScrolling(float /*dt*/) { if (_dragging) { this->unschedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::deaccelerateScrolling)); return; } float newX, newY; Vec2 maxInset, minInset; _container->setPosition(_container->getPosition() + _scrollDistance); if (_bounceable) { maxInset = _maxInset; minInset = _minInset; } else { maxInset = this->maxContainerOffset(); minInset = this->minContainerOffset(); } newX = _container->getPosition().x; newY = _container->getPosition().y; _scrollDistance = _scrollDistance * SCROLL_DEACCEL_RATE; this->setContentOffset(Vec2(newX,newY)); if ((fabsf(_scrollDistance.x) <= SCROLL_DEACCEL_DIST && fabsf(_scrollDistance.y) <= SCROLL_DEACCEL_DIST) || ((_direction == Direction::BOTH || _direction == Direction::VERTICAL) && (newY >= maxInset.y || newY <= minInset.y)) || ((_direction == Direction::BOTH || _direction == Direction::HORIZONTAL) && (newX >= maxInset.x || newX <= minInset.x))) { this->unschedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::deaccelerateScrolling)); this->relocateContainer(true); } } void ScrollViewSmooth::stoppedAnimatedScroll(Node * /*node*/) { this->unschedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::performedAnimatedScroll)); // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells. if (_delegate != nullptr) { _delegate->scrollViewDidScroll(this); } } void ScrollViewSmooth::performedAnimatedScroll(float /*dt*/) { if (_dragging) { this->unschedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::performedAnimatedScroll)); return; } if (_delegate != nullptr) { _delegate->scrollViewDidScroll(this); } } const Size& ScrollViewSmooth::getContentSize() const { return _container->getContentSize(); } void ScrollViewSmooth::setContentSize(const Size & size) { if (this->getContainer() != nullptr) { this->getContainer()->setContentSize(size); this->updateInset(); } } void ScrollViewSmooth::updateInset() { if (this->getContainer() != nullptr) { _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); } } /** * make sure all children go to the container */ void ScrollViewSmooth::addChild(Node * child, int zOrder, int tag) { if (_container != child) { _container->addChild(child, zOrder, tag); } else { Layer::addChild(child, zOrder, tag); } } void ScrollViewSmooth::removeChild(Node* node, bool cleanup) { if(_container != node) { _container->removeChild(node, cleanup); } else { Layer::removeChild(node, cleanup); } } void ScrollViewSmooth::removeAllChildrenWithCleanup(bool cleanup) { _container->removeAllChildrenWithCleanup(cleanup); Layer::removeAllChildrenWithCleanup(cleanup); } void ScrollViewSmooth::removeAllChildren() { removeAllChildrenWithCleanup(true); } void ScrollViewSmooth::addChild(Node * child, int zOrder, const std::string &name) { if (_container != child) { _container->addChild(child, zOrder, name); } else { Layer::addChild(child, zOrder, name); } } void ScrollViewSmooth::beforeDraw() { //ScrollView don't support drawing in 3D space _beforeDrawCommand.init(_globalZOrder); _beforeDrawCommand.func = CC_CALLBACK_0(ScrollViewSmooth::onBeforeDraw, this); Director::getInstance()->getRenderer()->addCommand(&_beforeDrawCommand); } /** * clip this view so that outside of the visible bounds can be hidden. */ void ScrollViewSmooth::onBeforeDraw() { if (_clippingToBounds) { _scissorRestored = false; Rect frame = getViewRect(); auto glview = Director::getInstance()->getOpenGLView(); if (glview->getVR() == nullptr) { if (glview->isScissorEnabled()) { _scissorRestored = true; _parentScissorRect = glview->getScissorRect(); //set the intersection of _parentScissorRect and frame as the new scissor rect if (frame.intersectsRect(_parentScissorRect)) { float x = MAX(frame.origin.x, _parentScissorRect.origin.x); float y = MAX(frame.origin.y, _parentScissorRect.origin.y); float xx = MIN(frame.origin.x + frame.size.width, _parentScissorRect.origin.x + _parentScissorRect.size.width); float yy = MIN(frame.origin.y + frame.size.height, _parentScissorRect.origin.y + _parentScissorRect.size.height); glview->setScissorInPoints(x, y, xx - x, yy - y); } } else { glEnable(GL_SCISSOR_TEST); glview->setScissorInPoints(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); } } } } void ScrollViewSmooth::afterDraw() { _afterDrawCommand.init(_globalZOrder); _afterDrawCommand.func = CC_CALLBACK_0(ScrollViewSmooth::onAfterDraw, this); Director::getInstance()->getRenderer()->addCommand(&_afterDrawCommand); } /** * retract what's done in beforeDraw so that there's no side effect to * other nodes. */ void ScrollViewSmooth::onAfterDraw() { if (_clippingToBounds) { auto glview = Director::getInstance()->getOpenGLView(); if (glview->getVR() == nullptr) { if (_scissorRestored) {//restore the parent's scissor rect glview->setScissorInPoints(_parentScissorRect.origin.x, _parentScissorRect.origin.y, _parentScissorRect.size.width, _parentScissorRect.size.height); } else { glDisable(GL_SCISSOR_TEST); } } } } void ScrollViewSmooth::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) { // quick return if not visible if (!isVisible()) { return; } uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT: // To ease the migration to v3.0, we still support the Mat4 stack, // but it is deprecated and your code should not rely on it Director* director = Director::getInstance(); CCASSERT(nullptr != director, "Director is null when setting matrix stack"); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); this->beforeDraw(); bool visibleByCamera = isVisitableByVisitingCamera(); if (!_children.empty()) { int i=0; // draw children zOrder < 0 for( ; i < _children.size(); i++ ) { Node *child = _children.at(i); if ( child->getLocalZOrder() < 0 ) { child->visit(renderer, _modelViewTransform, flags); } else { break; } } // this draw if (visibleByCamera) this->draw(renderer, _modelViewTransform, flags); // draw children zOrder >= 0 for( ; i < _children.size(); i++ ) { Node *child = _children.at(i); child->visit(renderer, _modelViewTransform, flags); } } else if (visibleByCamera) { this->draw(renderer, _modelViewTransform, flags); } this->afterDraw(); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } bool ScrollViewSmooth::onTouchBegan(Touch* touch, Event* /*event*/) { /* if (!this->isVisible() || !this->hasVisibleParents()) { return false; } Rect frame = getViewRect(); //dispatcher does not know about clipping. reject touches outside visible bounds. if (_touches.size() > 2 || _touchMoved || !frame.containsPoint(touch->getLocation())) { return false; } if (std::find(_touches.begin(), _touches.end(), touch) == _touches.end()) { _touches.push_back(touch); } if (_touches.size() == 1) { // scrolling _touchPoint = this->convertTouchToNodeSpace(touch); _touchMoved = false; _dragging = true; //dragging started _scrollDistance.setZero(); _touchLength = 0.0f; //zg __pageTouchBegan(); } else if (_touches.size() == 2) { _touchPoint = (this->convertTouchToNodeSpace(_touches[0]).getMidpoint( this->convertTouchToNodeSpace(_touches[1]))); _touchLength = _container->convertTouchToNodeSpace(_touches[0]).getDistance( _container->convertTouchToNodeSpace(_touches[1])); _dragging = false; } return true;*/ if (_isInterceptTouch){ return false; } if (!this->isVisible() || !this->hasVisibleParents()) { return false; } Rect frame = getViewRect(); //dispatcher does not know about clipping. reject touches outside visible bounds. if (_touches.size() > 2 || _touchMoved || !frame.containsPoint(touch->getLocation())) { return false; } if (std::find(_touches.begin(), _touches.end(), touch) == _touches.end()) { _touches.push_back(touch); } if (_touches.size() == 1) { // scrolling _touchPoint = this->convertTouchToNodeSpace(touch); _touchMoved = false; _dragging = true; //dragging started _scrollDistance.setZero(); _touchLength = 0.0f; handlePressLogic(touch); //zg __pageTouchBegan(); } else if (_touches.size() == 2) { _touchPoint = (this->convertTouchToNodeSpace(_touches[0]).getMidpoint( this->convertTouchToNodeSpace(_touches[1]))); _touchLength = _container->convertTouchToNodeSpace(_touches[0]).getDistance( _container->convertTouchToNodeSpace(_touches[1])); _dragging = false; } return true; } void ScrollViewSmooth::onTouchMoved(Touch* touch, Event* /*event*/) { /* if (!this->isVisible()) { return; } if (std::find(_touches.begin(), _touches.end(), touch) != _touches.end()) { if (_touches.size() == 1 && _dragging) { // scrolling Vec2 moveDistance, newPoint; Rect frame; float newX, newY; frame = getViewRect(); newPoint = this->convertTouchToNodeSpace(_touches[0]); moveDistance = newPoint - _touchPoint; float dis = 0.0f; if (_direction == Direction::VERTICAL) { dis = moveDistance.y; float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } } else if (_direction == Direction::HORIZONTAL) { dis = moveDistance.x; float pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } else { dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y); float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } if (!_touchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH ) { //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); return; } if (!_touchMoved) { moveDistance.setZero(); } _touchPoint = newPoint; _touchMoved = true; if (_dragging) { switch (_direction) { case Direction::VERTICAL: moveDistance.set(0.0f, moveDistance.y); break; case Direction::HORIZONTAL: moveDistance.set(moveDistance.x, 0.0f); break; default: break; } newX = _container->getPosition().x + moveDistance.x; newY = _container->getPosition().y + moveDistance.y; _scrollDistance = moveDistance; this->setContentOffset(Vec2(newX, newY)); } } else if (_touches.size() == 2 && !_dragging) { const float len = _container->convertTouchToNodeSpace(_touches[0]).getDistance( _container->convertTouchToNodeSpace(_touches[1])); this->setZoomScale(this->getZoomScale()*len/_touchLength); } }*/ if (!this->isVisible()) { return; } if (std::find(_touches.begin(), _touches.end(), touch) != _touches.end()) { if (_touches.size() == 1 && _dragging) { // scrolling Vec2 moveDistance, newPoint; Rect frame; float newX, newY; frame = getViewRect(); newPoint = this->convertTouchToNodeSpace(_touches[0]); moveDistance = newPoint - _touchPoint; float dis = 0.0f; if (_direction == Direction::VERTICAL) { dis = moveDistance.y; float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } } else if (_direction == Direction::HORIZONTAL) { dis = moveDistance.x; float pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } else { dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y); float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } if (!_touchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH ) { //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); return; } if (!_touchMoved) { moveDistance.setZero(); } _touchPoint = newPoint; _touchMoved = true; if (_dragging) { switch (_direction) { case Direction::VERTICAL: moveDistance.set(0.0f, moveDistance.y); break; case Direction::HORIZONTAL: moveDistance.set(moveDistance.x, 0.0f); break; default: moveDistance.set(moveDistance.x, moveDistance.y); break; } newX = _container->getPosition().x + moveDistance.x; newY = _container->getPosition().y + moveDistance.y; _scrollDistance = moveDistance; handleMoveLogic(touch); // this->setContentOffset(Vec2(newX, newY)); } } else if (_touches.size() == 2 && !_dragging) { const float len = _container->convertTouchToNodeSpace(_touches[0]).getDistance( _container->convertTouchToNodeSpace(_touches[1])); this->setZoomScale(this->getZoomScale()*len/_touchLength); } } } void ScrollViewSmooth::onTouchEnded(Touch* touch, Event* /*event*/) { /* if (!this->isVisible()) { return; } auto touchIter = std::find(_touches.begin(), _touches.end(), touch); if (touchIter != _touches.end()) { if (_touches.size() == 1 && _touchMoved) { // this->schedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::deaccelerateScrolling)); //zg if (__pageTouchEnd()) { __pageClearTouch(); } else { // this->schedule(CC_SCHEDULE_SELECTOR(ScrollViewSmooth::deaccelerateScrolling)); } } _touches.erase(touchIter); } if (_touches.size() == 0) { _dragging = false; _touchMoved = false; }*/ if (!this->isVisible()) { return; } auto touchIter = std::find(_touches.begin(), _touches.end(), touch); _touches.erase(touchIter); if (_touches.size() == 0) { _dragging = false; _touchMoved = false; } if (!_isInterceptTouch) { handleReleaseLogic(touch); } _isInterceptTouch = false; } void ScrollViewSmooth::onTouchCancelled(Touch* touch, Event* /*event*/) {/* if (!this->isVisible()) { return; } auto touchIter = std::find(_touches.begin(), _touches.end(), touch); _touches.erase(touchIter); if (_touches.size() == 0) { _dragging = false; _touchMoved = false; }*/ if (!this->isVisible()) { return; } auto touchIter = std::find(_touches.begin(), _touches.end(), touch); _touches.erase(touchIter); if (_touches.size() == 0) { _dragging = false; _touchMoved = false; } if (!_isInterceptTouch) { handleReleaseLogic(touch); } _isInterceptTouch = false; } Rect ScrollViewSmooth::getViewRect() { Vec2 screenPos = this->convertToWorldSpace(Vec2::ZERO); float scaleX = this->getScaleX(); float scaleY = this->getScaleY(); for (Node *p = _parent; p != nullptr; p = p->getParent()) { scaleX *= p->getScaleX(); scaleY *= p->getScaleY(); } // Support negative scaling. Not doing so causes intersectsRect calls // (eg: to check if the touch was within the bounds) to return false. // Note, Node::getScale will assert if X and Y scales are different. if(scaleX<0.f) { screenPos.x += _viewSize.width*scaleX; scaleX = -scaleX; } if(scaleY<0.f) { screenPos.y += _viewSize.height*scaleY; scaleY = -scaleY; } return Rect(screenPos.x, screenPos.y, _viewSize.width*scaleX, _viewSize.height*scaleY); } // zg void ScrollViewSmooth::__pageTouchBegan() { //仅在设置了分页属性, 并且只有一个滑动方向的时候, 才支持分页. if( !m_bPaged || ( _direction != Direction::HORIZONTAL && _direction != Direction::VERTICAL )) return ; //记录初试时间和位置 m_touchBeganTime = clock(); m_touchBeganOffset = _direction == Direction::HORIZONTAL ? getContentOffset().x : getContentOffset().y; } bool ScrollViewSmooth::__pageTouchEnd() { if( !m_bPaged || ( _direction != Direction::HORIZONTAL && _direction != Direction::VERTICAL)) return false ; //constant const float PAGE_DISTENCE = _direction == Direction::HORIZONTAL ? getViewSize().width + m_pageAdjustSize.width: getViewSize().height + m_pageAdjustSize.height; if( PAGE_DISTENCE <= 0 ) return false; const float MAX_PAGE = ( _direction == Direction::HORIZONTAL ? getContentSize().width : getContentSize().height ) / PAGE_DISTENCE - 1; const float MIN_PAGE = 0; float currOffset = _direction == Direction::HORIZONTAL ? getContentOffset().x : getContentOffset().y; float deltaOffset = -(currOffset - m_touchBeganOffset); clock_t currTime = clock(); float speed = currTime != m_touchBeganTime ? deltaOffset / ( currTime - m_touchBeganTime ) : 0; m_targetPage = m_currPage; if( std::abs(deltaOffset) >= TURN_PAGE_MIN_OFFSET_RATIO * PAGE_DISTENCE ) {//滑动距离大于某一阈值. if( deltaOffset > 0 ) { m_targetPage = m_currPage + 1; } else if( deltaOffset < 0 ) { m_targetPage = m_currPage - 1; } } else if( std::abs(speed) >= TURN_PAGE_SPEED ) {//速度大于某一阈值. if( speed > 0 ) { m_targetPage = m_currPage + 1; } else if( speed < 0 ) { m_targetPage = m_currPage - 1; } } if( m_targetPage > MAX_PAGE ) { m_targetPage = MAX_PAGE; } else if( m_targetPage < MIN_PAGE ) { m_targetPage = MIN_PAGE; } float targetOffset = -m_targetPage * ( _direction == Direction::HORIZONTAL ? getViewSize().width + m_pageAdjustSize.width: getViewSize().height + m_pageAdjustSize.height); if (m_targetPage == MAX_PAGE) { targetOffset -= (_direction == Direction::HORIZONTAL ? m_pageAdjustSize.width : m_pageAdjustSize.height); } float pageDurateion = 0.2; Point targetPointOffset = _direction == Direction::HORIZONTAL ? Point( targetOffset, getContentOffset().y ) : Point(getContentOffset().x, targetOffset ); setContentOffsetInDuration(targetPointOffset, pageDurateion); if(_callFunc && (m_currPage != m_targetPage)){ (this->*_callFunc)(m_targetPage); } m_currPage = m_targetPage; return true; } void ScrollViewSmooth::__pageTouchCancel() { if( !m_bPaged || ( _direction != Direction::HORIZONTAL && _direction != Direction::VERTICAL )) return ; __pageClearTouch(); } void ScrollViewSmooth::__pageClearTouch() { //clear所有状态 m_touchBeganOffset = 0; m_touchBeganTime = 0; m_targetPage = m_currPage; } void ScrollViewSmooth::zgMoveToPage(int page) { if (page != m_currPage) { float targetOffset = -page * (_direction == Direction::HORIZONTAL ? getViewSize().width : getViewSize().height); Point targetPointOffset = _direction == Direction::HORIZONTAL ? Point( targetOffset, getContentOffset().y ) : Point(getContentOffset().x, targetOffset ); setContentOffset(targetPointOffset); m_currPage = page; if(_callFunc){ (this->*_callFunc)(m_currPage); } } } void ScrollViewSmooth::zgMoveToPageWithAnimation(int page) { if (page != m_currPage) { float targetOffset = -page * (_direction == Direction::HORIZONTAL ? getViewSize().width : getViewSize().height); float pageDurateion = 0.2; Point targetPointOffset = _direction == Direction::HORIZONTAL ? Point( targetOffset, getContentOffset().y ) : Point(getContentOffset().x, targetOffset ); setContentOffsetInDuration(targetPointOffset, pageDurateion); m_currPage = page; if(_callFunc){ (this->*_callFunc)(m_currPage); } } } #pragma mask UItouch void ScrollViewSmooth::handlePressLogic(Touch *touch){ _bePressed = true; _autoScrolling = false; // Clear gathered touch move information { _touchMovePreviousTimestamp = utils::getTimeInMilliseconds(); _touchMoveDisplacements.clear(); _touchMoveTimeDeltas.clear(); } } void ScrollViewSmooth::handleMoveLogic(Touch *touch){ Vec2 currPt, prevPt; // if(!calculateCurrAndPrevTouchPoints(touch, &currPt, &prevPt)) // { // return; // } currPt = convertTouchToNodeSpace(touch); prevPt = convertToNodeSpace(touch->getPreviousLocation()); Vec2 delta = currPt - prevPt; // Vec2 delta(delta3.x, delta3.y); scrollChildren(delta); // Gather touch move information for speed calculation // 收集触摸移动信息以进行速度计算 gatherTouchMove(delta); } void ScrollViewSmooth::handleReleaseLogic(Touch *touch){ // Gather the last touch information when released { Vec2 currPt, prevPt; currPt = convertTouchToNodeSpace(touch); prevPt = convertToNodeSpace(touch->getPreviousLocation()); Vec2 delta = currPt - prevPt; gatherTouchMove(delta); } _bePressed = false; bool bounceBackStarted = startBounceBackIfNeeded(); // bool bounceBackStarted = false; // if(!bounceBackStarted && _inertiaScrollEnabled) if(!bounceBackStarted) { Vec2 touchMoveVelocity = calculateTouchMoveVelocity(); if(touchMoveVelocity != Vec2::ZERO) { startInertiaScroll(touchMoveVelocity); } } } void ScrollViewSmooth::scrollChildren(const Vec2& deltaMove) { Vec2 realMove = deltaMove; if(_bounceable) { // If the position of the inner container is out of the boundary, the offsets should be divided by two. Vec2 outOfBoundary = getHowMuchOutOfBoundary(); // log("outOfBoundaryX %f, outOfBoundaryY %f",outOfBoundary.x,outOfBoundary.y);//手没离开的时候计算滑动用的log realMove.x *= (outOfBoundary.x == 0 ? 1 : 0.5f); realMove.y *= (outOfBoundary.y == 0 ? 1 : 0.5f); } if(!_bounceable) { Vec2 outOfBoundary = getHowMuchOutOfBoundary(realMove); realMove += outOfBoundary; } // bool scrolledToLeft = false; // bool scrolledToRight = false; // bool scrolledToTop = false; // bool scrolledToBottom = false; // if (realMove.y > 0.0f) // up // { // float icBottomPos = _container->getBottomBoundary(); // if (icBottomPos + realMove.y >= 0) // { // scrolledToBottom = true; // } // } // else if (realMove.y < 0.0f) // down // { // float icTopPos = _innerContainer->getTopBoundary(); // if (icTopPos + realMove.y <= _topBoundary) // { // scrolledToTop = true; // } // } // // if (realMove.x < 0.0f) // left // { // float icRightPos = _innerContainer->getRightBoundary(); // if (icRightPos + realMove.x <= _rightBoundary) // { // scrolledToRight = true; // } // } // else if (realMove.x > 0.0f) // right // { // float icLeftPos = _innerContainer->getLeftBoundary(); // if (icLeftPos + realMove.x >= _leftBoundary) // { // scrolledToLeft = true; // } // } moveInnerContainer(realMove, false); } Vec2 ScrollViewSmooth::getHowMuchOutOfBoundary(const Vec2& addition) { // if(addition == Vec2::ZERO && !_outOfBoundaryAmountDirty) // { // return _outOfBoundaryAmount; // } // Vec2 outOfBoundaryAmount(Vec2::ZERO); // if(_innerContainer->getLeftBoundary() + addition.x > _leftBoundary) // { // outOfBoundaryAmount.x = _leftBoundary - (_innerContainer->getLeftBoundary() + addition.x); // } // else if(_innerContainer->getRightBoundary() + addition.x < _rightBoundary) // { // outOfBoundaryAmount.x = _rightBoundary - (_innerContainer->getRightBoundary() + addition.x); // } // // if(_innerContainer->getTopBoundary() + addition.y < _topBoundary) // { // outOfBoundaryAmount.y = _topBoundary - (_innerContainer->getTopBoundary() + addition.y); // } // else if(_innerContainer->getBottomBoundary() + addition.y > _bottomBoundary) // { // outOfBoundaryAmount.y = _bottomBoundary - (_innerContainer->getBottomBoundary() + addition.y); // } // // if(addition == Vec2::ZERO) // { // _outOfBoundaryAmount = outOfBoundaryAmount; // _outOfBoundaryAmountDirty = false; // } // return outOfBoundaryAmount; Vec2 outOfBoundaryAmount(Vec2::ZERO); const Vec2 minOffset = this->minContainerOffset(); const Vec2 maxOffset = this->maxContainerOffset(); if (_container->getPosition().y + addition.y < minOffset.y) { outOfBoundaryAmount.y = minOffset.y - (_container->getPosition().y + addition.y); } return outOfBoundaryAmount; } void ScrollViewSmooth::moveInnerContainer(const Vec2& deltaMove, bool canStartBounceBack) { Vec2 adjustedMove = flattenVectorByDirection(deltaMove); // log("moveInnerContainerX %f,moveInnerContainerY %f ,canStartBounceBack is %d",deltaMove.x,deltaMove.y,canStartBounceBack); // setInnerContainerPosition(getInnerContainerPosition() + adjustedMove); setContentOffset(getContentOffset() + adjustedMove); if(_bounceable && canStartBounceBack) { startBounceBackIfNeeded(); } } Vec2 ScrollViewSmooth::flattenVectorByDirection(const Vec2& vector) { Vec2 result = vector; // result.x = (_direction == Direction::VERTICAL || _direction == Direction::BOTH ? 0 : result.x); // result.y = (_direction == Direction::HORIZONTAL || _direction == Direction::BOTH ? 0 : result.y); result.x = (_direction == Direction::VERTICAL ? 0 : result.x); result.y = (_direction == Direction::HORIZONTAL ? 0 : result.y); return result; } void ScrollViewSmooth::gatherTouchMove(const Vec2& delta) { while(_touchMoveDisplacements.size() >= 5) { _touchMoveDisplacements.pop_front(); _touchMoveTimeDeltas.pop_front(); } _touchMoveDisplacements.push_back(delta); long long timestamp = utils::getTimeInMilliseconds(); _touchMoveTimeDeltas.push_back((timestamp - _touchMovePreviousTimestamp) / 1000.0f); _touchMovePreviousTimestamp = timestamp; } Vec2 ScrollViewSmooth::calculateTouchMoveVelocity() const { float totalTime = 0; // log("------------------------------------------"); for(auto &timeDelta : _touchMoveTimeDeltas) { // log("------- timeDelta %f", timeDelta); totalTime += timeDelta; } // log("------- totalTime %f", totalTime); if(totalTime == 0/* || totalTime >= 0.5*/) { // log("------- totalTime changed is not good"); return Vec2::ZERO; } if (totalTime < 0.01) { // log("------- totalTime changed1 ---------- %f", totalTime); totalTime = 0.2; } else if (totalTime < 0.02 && totalTime >= 0.01) { // log("------- totalTime changed2 ---------- %f", totalTime); totalTime = 0.02; } else if (totalTime >= 0.5) { // log("------- totalTime changed3 ---------- %f", totalTime); totalTime = 0.5; } // log("------------------------------------------"); Vec2 totalMovement; for(auto &displacement : _touchMoveDisplacements) { totalMovement += displacement; } return totalMovement / totalTime; } void ScrollViewSmooth::startInertiaScroll(const Vec2& touchMoveVelocity) { const float MOVEMENT_FACTOR = 0.7f; Vec2 inertiaTotalMovement = touchMoveVelocity * MOVEMENT_FACTOR; startAttenuatingAutoScroll(inertiaTotalMovement, touchMoveVelocity); } void ScrollViewSmooth::startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity) { float time = calculateAutoScrollTimeByInitialSpeed(initialVelocity.length()); startAutoScroll(deltaMove, time, true); } bool ScrollViewSmooth::isNecessaryAutoScrollBrake() { if(_autoScrollBraking) { return true; } Vec2 bounceBackAmount = getHowMuchOutOfBoundary(); if(!fltEqualZero(bounceBackAmount)) { // It just went out of boundary. if(!_autoScrollCurrentlyOutOfBoundary) { _autoScrollCurrentlyOutOfBoundary = true; _autoScrollBraking = true; _autoScrollBrakingStartPosition = _container->getPosition(); return true; } } else { _autoScrollCurrentlyOutOfBoundary = false; } return false; } bool ScrollViewSmooth::startBounceBackIfNeeded() { if (!_bounceable) { return false; } Vec2 bounceBackAmount = getHowMuchOutOfBoundary(); if(fltEqualZero(bounceBackAmount)) { return false; } startAutoScroll(bounceBackAmount, 1.0f, true); return true; } float ScrollViewSmooth::calculateAutoScrollTimeByInitialSpeed(float initialSpeed) { // Calculate the time from the initial speed according to quintic polynomial. float time = sqrtf(sqrtf(initialSpeed / 5)); return time; } void ScrollViewSmooth::startAutoScroll(const Vec2& deltaMove, float timeInSec, bool attenuated) { Vec2 adjustedDeltaMove = flattenVectorByDirection(deltaMove);// 单方向 _autoScrolling = true; _autoScrollTargetDelta = adjustedDeltaMove; // _autoScrollAttenuate = attenuated; _autoScrollStartPosition = getContentOffset(); _autoScrollTotalTime = timeInSec; _autoScrollAccumulatedTime = 0; _autoScrollBraking = false; _autoScrollBrakingStartPosition = Vec2::ZERO; // If the destination is also out of boundary of same side, start brake from beginning. Vec2 currentOutOfBoundary = getHowMuchOutOfBoundary(); if (!fltEqualZero(currentOutOfBoundary)) { _autoScrollCurrentlyOutOfBoundary = true; Vec2 afterOutOfBoundary = getHowMuchOutOfBoundary(adjustedDeltaMove); if(currentOutOfBoundary.x * afterOutOfBoundary.x > 0 || currentOutOfBoundary.y * afterOutOfBoundary.y > 0) { _autoScrollBraking = true; } } } #pragma mask update void ScrollViewSmooth::update(float dt) { if (_autoScrolling) { processAutoScrolling(dt); } } //手离开的时候计算滑动用的log void ScrollViewSmooth::processAutoScrolling(float deltaTime) { // Make auto scroll shorter if it needs to deaccelerate. //如果需要减速,请使自动滚动更短。 // float brakingFactor = (/* DISABLES CODE */ (false) ? 0.05 : 1); float brakingFactor = (isNecessaryAutoScrollBrake() ? 0.05 : 1); // log("brakingFactor is %f",brakingFactor); // Elapsed time _autoScrollAccumulatedTime += deltaTime * (1 / brakingFactor); // Calculate the progress percentage float percentage = MIN(1, _autoScrollAccumulatedTime / _autoScrollTotalTime); bool _autoScrollAttenuate = true; if(_autoScrollAttenuate) { // Use quintic(5th degree) polynomial percentage = tweenfunc::quintEaseOut(percentage); } // Calculate the new position Vec2 newPosition = _autoScrollStartPosition + (_autoScrollTargetDelta * percentage); bool reachedEnd = std::abs(percentage - 1) <= FLT_EPSILON; if (reachedEnd) { newPosition = _autoScrollStartPosition + _autoScrollTargetDelta; } if(_bounceable) { // The new position is adjusted if out of boundary newPosition = _autoScrollBrakingStartPosition + (newPosition - _autoScrollBrakingStartPosition) * brakingFactor; } else { // Don't let go out of boundary // Vec2 moveDelta = newPosition - getContentOffset(); // Vec2 outOfBoundary = getHowMuchOutOfBoundary(moveDelta); // if (!fltEqualZero(outOfBoundary)) // { // newPosition += outOfBoundary; // reachedEnd = true; // } } // Finish auto scroll if it ended if(reachedEnd) { _autoScrolling = false; // dispatchEvent(SCROLLVIEW_EVENT_AUTOSCROLL_ENDED, EventType::AUTOSCROLL_ENDED); } moveInnerContainer(newPosition - getContentOffset(), reachedEnd); } bool ScrollViewSmooth::fltEqualZero(const Vec2& point) const { return (fabsf(point.x) <= 0.0001f && fabsf(point.y) <= 0.0001f); } void ScrollViewSmooth::onEnter(){ Layer::onEnter(); scheduleUpdate(); } NS_CC_EXT_END