diff --git a/cocos/ui/UIScrollView.cpp b/cocos/ui/UIScrollView.cpp index 2c23915930..34f405b159 100644 --- a/cocos/ui/UIScrollView.cpp +++ b/cocos/ui/UIScrollView.cpp @@ -31,11 +31,10 @@ THE SOFTWARE. #include "2d/CCCamera.h" NS_CC_BEGIN -namespace ui { - -static const float INERTIA_DEACCELERATION = 700.0f; -static const float INERTIA_VELOCITY_MAX = 2500; +static const float OUT_OF_BOUND_TIME_RESCALE = 0.05f; +static const float OUT_OF_BOUND_DISTANCE_RESCALE = 0.05f; static const float BOUNCE_BACK_DURATION = 1.0f; + #define MOVE_INCH 7.0f/160.0f static float convertDistanceFromPointToInch(const Vec2& dis) @@ -46,6 +45,8 @@ static float convertDistanceFromPointToInch(const Vec2& dis) return distance; } +namespace ui { + IMPLEMENT_CLASS_GUI_INFO(ScrollView) ScrollView::ScrollView(): @@ -57,17 +58,17 @@ _leftBoundary(0.0f), _rightBoundary(0.0f), _bePressed(false), _childFocusCancelOffsetInInch(MOVE_INCH), -_inertiaScrollEnabled(true), -_inertiaScrolling(false), -_inertiaPrevTouchTimestamp(0), -_inertiaScrollExpectedTime(0), -_inertiaScrollElapsedTime(0), +_touchMovePreviousTimestamp(0), _autoScrolling(false), _autoScrollAttenuate(true), _autoScrollDuration(0), _autoScrollAccumulatedTime(0), +_autoScrollCompleteCallback(nullptr), +_autoScrollMoveCallback(nullptr), +_outOfBoundaryDuringInertiaScroll(false), +_inertiaScrollEnabled(true), +_inertiaScrolling(false), _bounceEnabled(false), -_bouncingBack(false), _scrollBarEnabled(true), _verticalScrollBar(nullptr), _horizontalScrollBar(nullptr), @@ -223,6 +224,10 @@ const Size& ScrollView::getInnerContainerSize() const void ScrollView::setInnerContainerPosition(const Vec2 &position) { + if(position == _innerContainer->getPosition()) + { + return; + } _innerContainer->setPosition(position); this->retain(); @@ -340,8 +345,27 @@ bool ScrollView::startBounceBackIfNeeded() return false; } - _bouncingBack = true; - startAutoScroll(outOfBoundary, BOUNCE_BACK_DURATION, true); + startAutoScroll(outOfBoundary, BOUNCE_BACK_DURATION, true, + nullptr, + [this](const Vec2& moveDelta) { + if(moveDelta.x > 0) + { + processScrollEvent(MoveDirection::RIGHT, true); + } + else if(moveDelta.x < 0) + { + processScrollEvent(MoveDirection::LEFT, true); + } + if(moveDelta.y > 0) + { + processScrollEvent(MoveDirection::TOP, true); + } + else if(moveDelta.y < 0) + { + processScrollEvent(MoveDirection::BOTTOM, true); + } + } + ); return true; } @@ -369,57 +393,16 @@ Vec2 ScrollView::getHowMuchOutOfBoundary(const Vec2& addition) const return result; } -void ScrollView::processAutoScrolling(float deltaTime) -{ - _autoScrollAccumulatedTime += deltaTime; - float percentage = _autoScrollAccumulatedTime / _autoScrollDuration; - if(percentage >= 1) - { - moveChildrenToPosition(_autoScrollStartPosition + _autoScrollTargetDelta); - _autoScrolling = false; - _bouncingBack = false; - } - else - { - if(_autoScrollAttenuate) - { - percentage = tweenfunc::quintEaseOut(percentage); - } - Vec2 moveDelta = _autoScrollTargetDelta * percentage; - moveChildrenToPosition(_autoScrollStartPosition + moveDelta); - - // Dispatch related events if bouncing - if(_bouncingBack) - { - if(moveDelta.x > 0) - { - processScrollEvent(MoveDirection::RIGHT, true); - } - else if(moveDelta.x < 0) - { - processScrollEvent(MoveDirection::LEFT, true); - } - if(moveDelta.y > 0) - { - processScrollEvent(MoveDirection::TOP, true); - } - else if(moveDelta.y < 0) - { - processScrollEvent(MoveDirection::BOTTOM, true); - } - } - } -} - bool ScrollView::isOutOfBoundary(MoveDirection dir) const { switch(dir) { - case MoveDirection::TOP: return _innerContainer->getTopBoundary() < _topBoundary; - case MoveDirection::BOTTOM: return _innerContainer->getBottomBoundary() > _bottomBoundary; - case MoveDirection::LEFT: return _innerContainer->getLeftBoundary() > _leftBoundary; - case MoveDirection::RIGHT: return _innerContainer->getRightBoundary() < _rightBoundary; + case MoveDirection::TOP: return _innerContainer->getTopBoundary() < _topBoundary; + case MoveDirection::BOTTOM: return _innerContainer->getBottomBoundary() > _bottomBoundary; + case MoveDirection::LEFT: return _innerContainer->getLeftBoundary() > _leftBoundary; + case MoveDirection::RIGHT: return _innerContainer->getRightBoundary() < _rightBoundary; } + return false; } bool ScrollView::isOutOfBoundaryTopOrBottom() const @@ -432,7 +415,17 @@ bool ScrollView::isOutOfBoundaryLeftOrRight() const return isOutOfBoundary(MoveDirection::LEFT) || isOutOfBoundary(MoveDirection::RIGHT); } -void ScrollView::startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated) +bool ScrollView::isOutOfBoundary() const +{ + return isOutOfBoundaryTopOrBottom() || isOutOfBoundaryLeftOrRight(); +} + +void ScrollView::startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated) +{ + startAutoScroll(des - _innerContainer->getPosition(), second, attenuated); +} + +void ScrollView::startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated, std::function completeCallback, std::function moveCallback) { _autoScrolling = true; _autoScrollTargetDelta = deltaMove; @@ -440,11 +433,79 @@ void ScrollView::startAutoScroll(const Vec2& deltaMove, float duration, bool att _autoScrollStartPosition = _innerContainer->getPosition(); _autoScrollDuration = duration; _autoScrollAccumulatedTime = 0; + _autoScrollCompleteCallback = completeCallback; + _autoScrollMoveCallback = moveCallback; + _outOfBoundaryDuringInertiaScroll = false; + _outOfBoundaryPositionDuringInertiaScroll = Vec2::ZERO; } -void ScrollView::startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated) +void ScrollView::processAutoScrolling(float deltaTime) { - startAutoScroll(des - _innerContainer->getPosition(), second, attenuated); + // Shorten the auto scroll distance and time if it is out of boundary during inertia scroll. + float timeRescale = 1; + float distanceRescale = 1; + { + bool currentlyOutOfBoundDuringInertiaScroll = (_inertiaScrolling && isOutOfBoundary()); + if(!_outOfBoundaryDuringInertiaScroll && currentlyOutOfBoundDuringInertiaScroll) + { + _outOfBoundaryDuringInertiaScroll = true; + _outOfBoundaryPositionDuringInertiaScroll = getInnerContainerPosition(); + } + timeRescale = (_outOfBoundaryDuringInertiaScroll ? OUT_OF_BOUND_TIME_RESCALE : 1); + distanceRescale = (_outOfBoundaryDuringInertiaScroll ? OUT_OF_BOUND_DISTANCE_RESCALE : 1); + } + + // Calculate the percentage + float percentage = 0; + { + _autoScrollAccumulatedTime += deltaTime * (1 / timeRescale); + percentage = MIN(1, _autoScrollAccumulatedTime / _autoScrollDuration); + if(_autoScrollAttenuate) + { + // Use quintic(5th degree) polynomial + percentage = tweenfunc::quintEaseOut(percentage); + } + } + + // Calculate the new position + Vec2 deltaFromInitialPosition = _autoScrollTargetDelta * percentage; + Vec2 newPosition = _autoScrollStartPosition + deltaFromInitialPosition; + bool reachedEnd = false; + + // Adjust the new position according to the bounce opiton + { + if(_bounceEnabled) + { + newPosition = _outOfBoundaryPositionDuringInertiaScroll + (newPosition - _outOfBoundaryPositionDuringInertiaScroll) * distanceRescale; + } + else + { + Vec2 moveDelta = newPosition - getInnerContainerPosition(); + Vec2 outOfBoundary = getHowMuchOutOfBoundary(moveDelta); + if(outOfBoundary != Vec2::ZERO) + { + newPosition += outOfBoundary; + reachedEnd = true; + } + } + } + + moveChildrenToPosition(newPosition); + + if(_autoScrollMoveCallback) + { + _autoScrollMoveCallback(getInnerContainerPosition() - newPosition); + } + + // Finish auto scroll if it ended + if(percentage == 1 || reachedEnd) + { + _autoScrolling = false; + if(_autoScrollCompleteCallback) + { + _autoScrollCompleteCallback(); + } + } } void ScrollView::jumpToDestination(const Vec2 &des) @@ -462,75 +523,43 @@ void ScrollView::jumpToDestination(const Vec2 &des) moveChildrenToPosition(Vec2(finalOffsetX, finalOffsetY)); } -void ScrollView::startInertiaScroll() +Vec2 ScrollView::calculateTouchMoveVelocity() const { float totalDuration = 0; - for(auto &timeDelta : _inertiaTouchTimeDeltas) - { - totalDuration += timeDelta; - } + for(auto &timeDelta : _touchMoveTimeDeltas) + { + totalDuration += timeDelta; + } if(totalDuration == 0 || totalDuration >= 0.5f) { - return; + return Vec2::ZERO; } - _inertiaScrolling = true; - - // Calcualte the initial velocity Vec2 totalMovement; - for(auto &displacement : _inertiaTouchDisplacements) - { - totalMovement += displacement; - } - - for(auto i = _inertiaTouchDisplacements.begin(); i != _inertiaTouchDisplacements.end(); ++i) + for(auto &displacement : _touchMoveDisplacements) { - totalMovement += (*i); + totalMovement += displacement; } - totalMovement.x = (_direction == Direction::VERTICAL ? 0 : totalMovement.x); - totalMovement.y = (_direction == Direction::HORIZONTAL ? 0 : totalMovement.y); - - _inertiaInitiVelocity = totalMovement / totalDuration; - _inertiaInitiVelocity.x = MIN(_inertiaInitiVelocity.x, INERTIA_VELOCITY_MAX); - _inertiaInitiVelocity.y = MIN(_inertiaInitiVelocity.y, INERTIA_VELOCITY_MAX); - _inertiaInitiVelocity.x = MAX(_inertiaInitiVelocity.x, -INERTIA_VELOCITY_MAX); - _inertiaInitiVelocity.y = MAX(_inertiaInitiVelocity.y, -INERTIA_VELOCITY_MAX); - - // Calculate values for ease out - _inertiaScrollExpectedTime = _inertiaInitiVelocity.length() / INERTIA_DEACCELERATION; - _inertiaScrollElapsedTime = 0; + return totalMovement / totalDuration; } -void ScrollView::processInertiaScrolling(float dt) +void ScrollView::startInertiaScroll(const Vec2& touchMoveVelocity) { - _inertiaScrollElapsedTime += dt; - if(isOutOfBoundaryLeftOrRight() || isOutOfBoundaryTopOrBottom()) - { - // If the inner container is out of boundary, shorten the inertia time. - _inertiaScrollElapsedTime += dt * (45000 / INERTIA_DEACCELERATION); - } - float percentage = _inertiaScrollElapsedTime / _inertiaScrollExpectedTime; - if(percentage >= 1) - { + Vec2 initialVelocity = touchMoveVelocity; + initialVelocity.x = (_direction == Direction::VERTICAL ? 0 : initialVelocity.x); + initialVelocity.y = (_direction == Direction::HORIZONTAL ? 0 : initialVelocity.y); + + const float MOVEMENT_FACTOR = 0.7f; + Vec2 inertiaTotalDisplacement = initialVelocity * MOVEMENT_FACTOR; + + // Calculate the duration from the initial velocity according to quintic polynomial. + float duration = sqrtf(sqrtf(initialVelocity.length() / 5)); + + _inertiaScrolling = true; + startAutoScroll(inertiaTotalDisplacement, duration, true, [this]() { _inertiaScrolling = false; startBounceBackIfNeeded(); - return; - } - percentage = tweenfunc::quartEaseOut(percentage); - - Vec2 inertiaVelocity = _inertiaInitiVelocity * (1 - percentage); - Vec2 displacement = inertiaVelocity * dt; - if(!_bounceEnabled) - { - Vec2 outOfBoundary = getHowMuchOutOfBoundary(displacement); - if(outOfBoundary != Vec2::ZERO) - { - // Don't allow to go out of boundary - displacement += outOfBoundary; - _inertiaScrolling = false; - } - } - moveChildren(displacement.x, displacement.y); + }); } bool ScrollView::scrollChildren(float touchOffsetX, float touchOffsetY) @@ -803,36 +832,18 @@ void ScrollView::jumpToPercentBothDirection(const Vec2& percent) jumpToDestination(Vec2(-(percent.x * w / 100.0f), minY + percent.y * h / 100.0f)); } -void ScrollView::startRecordSlidAction() -{ - if (_inertiaScrolling) - { - _inertiaScrolling = false; - } - if(_autoScrolling) - { - _autoScrolling = false; - _bouncingBack = false; - } -} - -void ScrollView::endRecordSlidAction() -{ - bool bounceBackStarted = startBounceBackIfNeeded(); - if(!bounceBackStarted && _inertiaScrollEnabled) - { - startInertiaScroll(); - } -} - void ScrollView::handlePressLogic(Touch *touch) { - startRecordSlidAction(); _bePressed = true; - - _inertiaPrevTouchTimestamp = utils::getTimeInMilliseconds(); - _inertiaTouchDisplacements.clear(); - _inertiaTouchTimeDeltas.clear(); + _autoScrolling = false; + _inertiaScrolling = false; + + // Initialize touch move information + { + _touchMovePreviousTimestamp = utils::getTimeInMilliseconds(); + _touchMoveDisplacements.clear(); + _touchMoveTimeDeltas.clear(); + } if(_verticalScrollBar != nullptr) { @@ -856,24 +867,36 @@ void ScrollView::handleMoveLogic(Touch *touch) Vec3 delta3 = currPt - prevPt; Vec2 delta(delta3.x, delta3.y); scrollChildren(delta.x, delta.y); - - while(_inertiaTouchDisplacements.size() > 5) + + // Gather touch move information for speed calculation { - _inertiaTouchDisplacements.pop_front(); - _inertiaTouchTimeDeltas.pop_front(); + 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; } - _inertiaTouchDisplacements.push_back(delta); - - long long timestamp = utils::getTimeInMilliseconds(); - _inertiaTouchTimeDeltas.push_back((timestamp - _inertiaPrevTouchTimestamp) / 1000.0f); - _inertiaPrevTouchTimestamp = timestamp; } void ScrollView::handleReleaseLogic(Touch *touch) { - endRecordSlidAction(); _bePressed = false; + bool bounceBackStarted = startBounceBackIfNeeded(); + if(!bounceBackStarted && _inertiaScrollEnabled) + { + Vec2 touchMoveVelocity = calculateTouchMoveVelocity(); + if(touchMoveVelocity != Vec2::ZERO) + { + startInertiaScroll(touchMoveVelocity); + } + } + if(_verticalScrollBar != nullptr) { _verticalScrollBar->onTouchEnded(); @@ -928,11 +951,7 @@ void ScrollView::onTouchCancelled(Touch *touch, Event *unusedEvent) void ScrollView::update(float dt) { - if (_inertiaScrolling) - { - processInertiaScrolling(dt); - } - else if (_autoScrolling) + if (_autoScrolling) { processAutoScrolling(dt); } @@ -1002,29 +1021,29 @@ void ScrollView::processScrollEvent(MoveDirection dir, bool bounce) ScrollviewEventType scrollEventType; EventType eventType; switch(dir) { - case MoveDirection::TOP: - { - scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_TOP : SCROLLVIEW_EVENT_SCROLL_TO_TOP); - eventType = (bounce ? EventType::BOUNCE_TOP : EventType::SCROLL_TO_TOP); - break; - } - case MoveDirection::BOTTOM: - { - scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_BOTTOM : SCROLLVIEW_EVENT_SCROLL_TO_BOTTOM); - eventType = (bounce ? EventType::BOUNCE_BOTTOM : EventType::SCROLL_TO_BOTTOM); - break; - } - case MoveDirection::LEFT: - { - scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_LEFT : SCROLLVIEW_EVENT_SCROLL_TO_LEFT); - eventType = (bounce ? EventType::BOUNCE_LEFT : EventType::SCROLL_TO_LEFT); - break; - } - case MoveDirection::RIGHT: - { - scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_RIGHT : SCROLLVIEW_EVENT_SCROLL_TO_RIGHT); - eventType = (bounce ? EventType::BOUNCE_RIGHT : EventType::SCROLL_TO_RIGHT); - break; + case MoveDirection::TOP: + { + scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_TOP : SCROLLVIEW_EVENT_SCROLL_TO_TOP); + eventType = (bounce ? EventType::BOUNCE_TOP : EventType::SCROLL_TO_TOP); + break; + } + case MoveDirection::BOTTOM: + { + scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_BOTTOM : SCROLLVIEW_EVENT_SCROLL_TO_BOTTOM); + eventType = (bounce ? EventType::BOUNCE_BOTTOM : EventType::SCROLL_TO_BOTTOM); + break; + } + case MoveDirection::LEFT: + { + scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_LEFT : SCROLLVIEW_EVENT_SCROLL_TO_LEFT); + eventType = (bounce ? EventType::BOUNCE_LEFT : EventType::SCROLL_TO_LEFT); + break; + } + case MoveDirection::RIGHT: + { + scrollEventType = (bounce ? SCROLLVIEW_EVENT_BOUNCE_RIGHT : SCROLLVIEW_EVENT_SCROLL_TO_RIGHT); + eventType = (bounce ? EventType::BOUNCE_RIGHT : EventType::SCROLL_TO_RIGHT); + break; } } dispatchEvent(scrollEventType, eventType); @@ -1351,22 +1370,22 @@ void ScrollView::copySpecialProperties(Widget *widget) _rightBoundary = scrollView->_rightBoundary; _bePressed = scrollView->_bePressed; _childFocusCancelOffsetInInch = scrollView->_childFocusCancelOffsetInInch; - setInertiaScrollEnabled(scrollView->_inertiaScrollEnabled); - _inertiaScrolling = scrollView->_inertiaScrolling; - _inertiaInitiVelocity = scrollView->_inertiaInitiVelocity; - _inertiaTouchDisplacements = scrollView->_inertiaTouchDisplacements; - _inertiaTouchTimeDeltas = scrollView->_inertiaTouchTimeDeltas; - _inertiaPrevTouchTimestamp = scrollView->_inertiaPrevTouchTimestamp; - _inertiaScrollExpectedTime = scrollView->_inertiaScrollExpectedTime; - _inertiaScrollElapsedTime = scrollView->_inertiaScrollElapsedTime; + _touchMoveDisplacements = scrollView->_touchMoveDisplacements; + _touchMoveTimeDeltas = scrollView->_touchMoveTimeDeltas; + _touchMovePreviousTimestamp = scrollView->_touchMovePreviousTimestamp; _autoScrolling = scrollView->_autoScrolling; _autoScrollAttenuate = scrollView->_autoScrollAttenuate; _autoScrollStartPosition = scrollView->_autoScrollStartPosition; _autoScrollTargetDelta = scrollView->_autoScrollTargetDelta; _autoScrollDuration = scrollView->_autoScrollDuration; _autoScrollAccumulatedTime = scrollView->_autoScrollAccumulatedTime; + _outOfBoundaryDuringInertiaScroll = scrollView->_outOfBoundaryDuringInertiaScroll; + _outOfBoundaryPositionDuringInertiaScroll = scrollView->_outOfBoundaryPositionDuringInertiaScroll; + _autoScrollCompleteCallback = scrollView->_autoScrollCompleteCallback; + _autoScrollMoveCallback = scrollView->_autoScrollMoveCallback; + setInertiaScrollEnabled(scrollView->_inertiaScrollEnabled); + _inertiaScrolling = scrollView->_inertiaScrolling; setBounceEnabled(scrollView->_bounceEnabled); - _bouncingBack = scrollView->_bouncingBack; _scrollViewEventListener = scrollView->_scrollViewEventListener; _scrollViewEventSelector = scrollView->_scrollViewEventSelector; _eventCallback = scrollView->_eventCallback; diff --git a/cocos/ui/UIScrollView.h b/cocos/ui/UIScrollView.h index 8f311ee458..f6a1247649 100644 --- a/cocos/ui/UIScrollView.h +++ b/cocos/ui/UIScrollView.h @@ -568,14 +568,16 @@ protected: bool isOutOfBoundary(MoveDirection dir) const; bool isOutOfBoundaryTopOrBottom() const; bool isOutOfBoundaryLeftOrRight() const; + bool isOutOfBoundary() const; void moveChildren(float offsetX, float offsetY); void moveChildrenToPosition(const Vec2& position); - void startInertiaScroll(); - void processInertiaScrolling(float dt); + Vec2 calculateTouchMoveVelocity() const; - void startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated); + void startInertiaScroll(const Vec2& touchMoveVelocity); + + void startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated, std::function completeCallback = nullptr, std::function moveCallback = nullptr); void startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated); void processAutoScrolling(float deltaTime); @@ -585,10 +587,6 @@ protected: virtual bool scrollChildren(float touchOffsetX, float touchOffsetY); - void startRecordSlidAction(); - virtual void endRecordSlidAction(); - - //ScrollViewProtocol virtual void handlePressLogic(Touch *touch); virtual void handleMoveLogic(Touch *touch); virtual void handleReleaseLogic(Touch *touch); @@ -616,25 +614,28 @@ protected: bool _bePressed; float _childFocusCancelOffsetInInch; - - bool _inertiaScrollEnabled; - bool _inertiaScrolling; - Vec2 _inertiaInitiVelocity; - std::list _inertiaTouchDisplacements; - std::list _inertiaTouchTimeDeltas; - long long _inertiaPrevTouchTimestamp; - float _inertiaScrollExpectedTime; - float _inertiaScrollElapsedTime; - + + // Touch move speed + std::list _touchMoveDisplacements; + std::list _touchMoveTimeDeltas; + long long _touchMovePreviousTimestamp; + bool _autoScrolling; bool _autoScrollAttenuate; Vec2 _autoScrollStartPosition; Vec2 _autoScrollTargetDelta; float _autoScrollDuration; float _autoScrollAccumulatedTime; + bool _outOfBoundaryDuringInertiaScroll; + Vec2 _outOfBoundaryPositionDuringInertiaScroll; + std::function _autoScrollCompleteCallback; + std::function _autoScrollMoveCallback; + // Inertia scroll + bool _inertiaScrollEnabled; + bool _inertiaScrolling; + bool _bounceEnabled; - bool _bouncingBack; bool _scrollBarEnabled; ScrollViewBar* _verticalScrollBar;