Merge pull request #13646 from neokim/refactor_scrollview_inertia_scroll

Refactor scroll view's inertia scroll
This commit is contained in:
子龙山人 2015-09-07 22:38:20 +08:00
commit 75882e64a0
2 changed files with 228 additions and 208 deletions

View File

@ -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<void()> completeCallback, std::function<void(const Vec2&)> 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;

View File

@ -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<void()> completeCallback = nullptr, std::function<void(const Vec2&)> 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<Vec2> _inertiaTouchDisplacements;
std::list<float> _inertiaTouchTimeDeltas;
long long _inertiaPrevTouchTimestamp;
float _inertiaScrollExpectedTime;
float _inertiaScrollElapsedTime;
// Touch move speed
std::list<Vec2> _touchMoveDisplacements;
std::list<float> _touchMoveTimeDeltas;
long long _touchMovePreviousTimestamp;
bool _autoScrolling;
bool _autoScrollAttenuate;
Vec2 _autoScrollStartPosition;
Vec2 _autoScrollTargetDelta;
float _autoScrollDuration;
float _autoScrollAccumulatedTime;
bool _outOfBoundaryDuringInertiaScroll;
Vec2 _outOfBoundaryPositionDuringInertiaScroll;
std::function<void()> _autoScrollCompleteCallback;
std::function<void(const Vec2& moveDelta)> _autoScrollMoveCallback;
// Inertia scroll
bool _inertiaScrollEnabled;
bool _inertiaScrolling;
bool _bounceEnabled;
bool _bouncingBack;
bool _scrollBarEnabled;
ScrollViewBar* _verticalScrollBar;