Merge branch 'feature/elastic_inertia_scroll' into feature/elastic_scrollview

Apply elastic movement to inertia scroll
This commit is contained in:
Neo Kim 2015-06-22 14:50:12 +09:00
commit ff5dbd5a52
2 changed files with 167 additions and 364 deletions

View File

@ -29,38 +29,16 @@ NS_CC_BEGIN
namespace ui {
static const float AUTOSCROLLMAXSPEED = 1000.0f;
static const float INERTIA_DEACCELERATION = 3000.0f;
static const float BOUNCE_BACK_DURATION = 0.3f;
static char LOG_BUFFER[256];
char* time()
static long long getTimestamp()
{
int nBufferLength = 256;
// Get current local time
struct tm* ptm = NULL;
struct timeval tv;
{
gettimeofday (&tv, NULL);
ptm = localtime (&tv.tv_sec);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
// Format the date and time, down to a single second.
strftime (LOG_BUFFER, nBufferLength, "%Y-%m-%d %H:%M:%S", ptm);
size_t nCurrentLength = strlen(LOG_BUFFER);
// Compute milliseconds from microseconds.
long lMilliseconds = tv.tv_usec / 1000;
snprintf(LOG_BUFFER + nCurrentLength, nBufferLength - nCurrentLength, ".%03ld", lMilliseconds);
return LOG_BUFFER;
}
const Vec2 SCROLLDIR_UP(0.0f, 1.0f);
const Vec2 SCROLLDIR_DOWN(0.0f, -1.0f);
const Vec2 SCROLLDIR_LEFT(-1.0f, 0.0f);
const Vec2 SCROLLDIR_RIGHT(1.0f, 0.0f);
IMPLEMENT_CLASS_GUI_INFO(ScrollView)
ScrollView::ScrollView():
@ -70,21 +48,19 @@ _topBoundary(0.0f),
_bottomBoundary(0.0f),
_leftBoundary(0.0f),
_rightBoundary(0.0f),
_autoScroll(false),
_autoScrollAddUpTime(0.0f),
_autoScrollOriginalSpeed(0.0f),
_autoScrollAcceleration(-1000.0f),
_isAutoScrollSpeedAttenuated(false),
_needCheckAutoScrollDestination(false),
_bePressed(false),
_slidTime(0.0f),
_childFocusCancelOffset(5.0f),
_inertiaScrollEnabled(true),
_inertiaScrolling(false),
_inertiaPrevTouchTimestamp(0),
_inertiaScrollExpectedTime(0),
_inertiaScrollElapsedTime(0),
_autoScrolling(false),
_autoScrollAttenuate(true),
_autoScrollDuration(0),
_autoScrollAccumulatedTime(0),
_bounceEnabled(false),
_bouncingBack(false),
_bounceBackAttenuate(true),
_bounceBackDuration(0),
_bounceBackAccumulatedTime(0),
_inertiaScrollEnabled(true),
_scrollViewEventListener(nullptr),
_scrollViewEventSelector(nullptr),
_eventCallback(nullptr)
@ -289,56 +265,6 @@ void ScrollView::moveChildren(float offsetX, float offsetY)
_innerContainer->setPosition(position);
}
void ScrollView::autoScrollChildren(float dt)
{
float lastTime = _autoScrollAddUpTime;
_autoScrollAddUpTime += dt;
if (_isAutoScrollSpeedAttenuated)
{
float nowSpeed = _autoScrollOriginalSpeed + _autoScrollAcceleration * _autoScrollAddUpTime;
if (nowSpeed <= 0.0f)
{
stopAutoScrollChildren();
startBounceBackIfNeeded();
}
else
{
float timeParam = lastTime * 2 + dt;
float offset = (_autoScrollOriginalSpeed + _autoScrollAcceleration * timeParam * 0.5f) * dt;
float offsetX = offset * _autoScrollDir.x;
float offsetY = offset * _autoScrollDir.y;
if (!scrollChildren(offsetX, offsetY))
{
stopAutoScrollChildren();
startBounceBackIfNeeded();
}
}
}
else
{
if (_needCheckAutoScrollDestination)
{
float xOffset = _autoScrollDir.x * dt * _autoScrollOriginalSpeed;
float yOffset = _autoScrollDir.y * dt * _autoScrollOriginalSpeed;
bool notDone = checkCustomScrollDestination(&xOffset, &yOffset);
bool scrollCheck = scrollChildren(xOffset, yOffset);
if (!notDone || !scrollCheck)
{
stopAutoScrollChildren();
startBounceBackIfNeeded();
}
}
else
{
if (!scrollChildren(_autoScrollDir.x * dt * _autoScrollOriginalSpeed, _autoScrollDir.y * dt * _autoScrollOriginalSpeed))
{
stopAutoScrollChildren();
startBounceBackIfNeeded();
}
}
}
}
bool ScrollView::startBounceBackIfNeeded()
{
if (!_bounceEnabled)
@ -346,18 +272,14 @@ bool ScrollView::startBounceBackIfNeeded()
return false;
}
_bounceBackTargetDelta = getHowMuchOutOfBoundary(Vec2::ZERO);
if(_bounceBackTargetDelta == Vec2::ZERO)
Vec2 outOfBoundary = getHowMuchOutOfBoundary(Vec2::ZERO);
if(outOfBoundary == Vec2::ZERO)
{
return false;
}
_bouncingBack = true;
_bounceBackAttenuate = true;
_bounceBackStartPosition = _innerContainer->getPosition();
_bounceBackDuration = DEFAULT_BOUNCE_BACK_DURATION;
_bounceBackAccumulatedTime = 0;
startAutoScroll(outOfBoundary, BOUNCE_BACK_DURATION, true);
return true;
}
@ -385,24 +307,27 @@ Vec2 ScrollView::getHowMuchOutOfBoundary(const Vec2& addition) const
return result;
}
void ScrollView::processBounceBack(float deltaTime)
void ScrollView::processAutoScrolling(float deltaTime)
{
_bounceBackAccumulatedTime += deltaTime;
float percentage = _bounceBackAccumulatedTime / _bounceBackDuration;
_autoScrollAccumulatedTime += deltaTime;
float percentage = _autoScrollAccumulatedTime / _autoScrollDuration;
if(percentage >= 1)
{
_innerContainer->setPosition(_bounceBackStartPosition + _bounceBackTargetDelta);
_innerContainer->setPosition(_autoScrollStartPosition + _autoScrollTargetDelta);
_autoScrolling = false;
_bouncingBack = false;
}
else
{
if(_bounceBackAttenuate)
if(_autoScrollAttenuate)
{
percentage = tweenfunc::expoEaseOut(percentage);
}
Vec2 moveDelta = _bounceBackTargetDelta * percentage;
Vec2 moveDelta = _autoScrollTargetDelta * percentage;
// Dispatch related events
// Dispatch related events if bouncing
if(_bouncingBack)
{
if(moveDelta.x > 0)
{
processScrollEvent(MoveDirection::RIGHT, true);
@ -419,7 +344,8 @@ void ScrollView::processBounceBack(float deltaTime)
{
processScrollEvent(MoveDirection::BOTTOM, true);
}
_innerContainer->setPosition(_bounceBackStartPosition + moveDelta);
}
_innerContainer->setPosition(_autoScrollStartPosition + moveDelta);
}
}
@ -444,35 +370,19 @@ bool ScrollView::isOutOfBoundaryLeftOrRight() const
return isOutOfBoundary(MoveDirection::LEFT) || isOutOfBoundary(MoveDirection::RIGHT);
}
void ScrollView::startAutoScrollChildrenWithOriginalSpeed(const Vec2& dir, float v, bool attenuated, float acceleration)
void ScrollView::startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated)
{
stopAutoScrollChildren();
_autoScrollDir = dir;
_isAutoScrollSpeedAttenuated = attenuated;
_autoScrollOriginalSpeed = v;
_autoScroll = true;
_autoScrollAcceleration = acceleration;
_autoScrolling = true;
_autoScrollTargetDelta = deltaMove;
_autoScrollAttenuate = attenuated;
_autoScrollStartPosition = _innerContainer->getPosition();
_autoScrollDuration = duration;
_autoScrollAccumulatedTime = 0;
}
void ScrollView::startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated)
{
_needCheckAutoScrollDestination = false;
_autoScrollDestination = des;
Vec2 dis = des - _innerContainer->getPosition();
Vec2 dir = dis.getNormalized();
float orSpeed = 0.0f;
float acceleration = -1000.0f;
if (attenuated)
{
acceleration = (-(2 * dis.getLength())) / (second * second);
orSpeed = 2 * dis.getLength() / second;
}
else
{
_needCheckAutoScrollDestination = true;
orSpeed = dis.getLength() / second;
}
startAutoScrollChildrenWithOriginalSpeed(dir, orSpeed, attenuated, acceleration);
startAutoScroll(des - _innerContainer->getPosition(), second, attenuated);
}
void ScrollView::jumpToDestination(const Vec2 &des)
@ -490,137 +400,71 @@ void ScrollView::jumpToDestination(const Vec2 &des)
_innerContainer->setPosition(Vec2(finalOffsetX, finalOffsetY));
}
void ScrollView::stopAutoScrollChildren()
void ScrollView::startInertiaScroll()
{
_autoScroll = false;
_autoScrollOriginalSpeed = 0.0f;
_autoScrollAddUpTime = 0.0f;
if(_inertiaTouchTimeDeltas.empty())
{
return;
}
bool ScrollView::checkCustomScrollDestinationLeft(float* touchOffsetX, float* touchOffsetY)
float totalDuration = 0;
for(auto i = _inertiaTouchTimeDeltas.begin(); i != _inertiaTouchTimeDeltas.end(); ++i)
{
float icLeftPos = _innerContainer->getLeftBoundary();
if (icLeftPos + *touchOffsetX >= _autoScrollDestination.x)
{
*touchOffsetX = _autoScrollDestination.x - icLeftPos;
return false;
totalDuration += (*i);
}
return true;
if(totalDuration >= 0.5f)
{
return;
}
bool ScrollView::checkCustomScrollDestinationRight(float* touchOffsetX, float* touchOffsetY)
_inertiaScrolling = true;
// Calcualte the initial velocity
Vec2 totalMovement;
for(auto i = _inertiaTouchDisplacements.begin(); i != _inertiaTouchDisplacements.end(); ++i)
{
float icRightPos = _innerContainer->getRightBoundary();
if (icRightPos + *touchOffsetX <= _autoScrollDestination.x)
{
*touchOffsetX = _autoScrollDestination.x - icRightPos;
return false;
totalMovement += (*i);
}
return true;
totalMovement.x = (_direction == Direction::VERTICAL ? 0 : totalMovement.x);
totalMovement.y = (_direction == Direction::HORIZONTAL ? 0 : totalMovement.y);
_inertiaInitiVelocity = totalMovement / totalDuration;
// Calculate values for ease out
_inertiaScrollExpectedTime = _inertiaInitiVelocity.length() / INERTIA_DEACCELERATION;
_inertiaScrollElapsedTime = 0;
}
bool ScrollView::checkCustomScrollDestination(float* touchOffsetX, float* touchOffsetY)
void ScrollView::processInertiaScrolling(float dt)
{
bool scrollenabled = true;
switch (_direction)
_inertiaScrollElapsedTime += dt;
if(isOutOfBoundaryLeftOrRight() || isOutOfBoundaryTopOrBottom())
{
case Direction::VERTICAL:
// If the inner container is out of boundary, shorten the inertia time.
_inertiaScrollElapsedTime += dt * 15;
}
float percentage = _inertiaScrollElapsedTime / _inertiaScrollExpectedTime;
if(percentage >= 1)
{
if (_autoScrollDir.y > 0)
_inertiaScrolling = false;
startBounceBackIfNeeded();
return;
}
percentage = tweenfunc::expoEaseOut(percentage);
Vec2 inertiaVelocity = _inertiaInitiVelocity * (1 - percentage);
Vec2 displacement = inertiaVelocity * dt;
if(!_bounceEnabled)
{
float icBottomPos = _innerContainer->getBottomBoundary();
if (icBottomPos + *touchOffsetY >= _autoScrollDestination.y)
Vec2 outOfBoundary = getHowMuchOutOfBoundary(displacement);
if(outOfBoundary != Vec2::ZERO)
{
*touchOffsetY = _autoScrollDestination.y - icBottomPos;
scrollenabled = false;
// Don't allow to go out of boundary
displacement += outOfBoundary;
_inertiaScrolling = false;
}
}
else
{
float icBottomPos = _innerContainer->getBottomBoundary();
if (icBottomPos + *touchOffsetY <= _autoScrollDestination.y)
{
*touchOffsetY = _autoScrollDestination.y - icBottomPos;
scrollenabled = false;
}
}
break;
}
case Direction::HORIZONTAL:
{
if (_autoScrollDir.x > 0)
{
float icLeftPos = _innerContainer->getLeftBoundary();
if (icLeftPos + *touchOffsetX >= _autoScrollDestination.x)
{
*touchOffsetX = _autoScrollDestination.x - icLeftPos;
scrollenabled = false;
}
}
else
{
float icLeftPos = _innerContainer->getLeftBoundary();
if (icLeftPos + *touchOffsetX <= _autoScrollDestination.x)
{
*touchOffsetX = _autoScrollDestination.x - icLeftPos;
scrollenabled = false;
}
}
break;
}
case Direction::BOTH:
{
if (*touchOffsetY > 0.0f) // up
{
if (*touchOffsetX > 0.0f) // right
{
scrollenabled = checkCustomScrollDestinationLeft(touchOffsetX, touchOffsetY);
}
else if (*touchOffsetX < 0.0f) // left
{
scrollenabled = checkCustomScrollDestinationRight(touchOffsetX, touchOffsetY);
}
float icBottomPos = _innerContainer->getBottomBoundary();
if (icBottomPos + *touchOffsetY >= _autoScrollDestination.y)
{
*touchOffsetY = _autoScrollDestination.y - icBottomPos;
scrollenabled = false;
}
}
else if (*touchOffsetY < 0.0f) // down
{
if (*touchOffsetX < 0.0f) // left
{
scrollenabled = checkCustomScrollDestinationRight(touchOffsetX, touchOffsetY);
}
else if (*touchOffsetX > 0.0f) // right
{
scrollenabled = checkCustomScrollDestinationLeft(touchOffsetX, touchOffsetY);
}
float icTopPos = _innerContainer->getTopBoundary();
if (icTopPos + *touchOffsetY <= _autoScrollDestination.y)
{
*touchOffsetY = _autoScrollDestination.y - icTopPos;
scrollenabled = false;
}
}
else
{
if (*touchOffsetX < 0.0f) // left
{
scrollenabled = checkCustomScrollDestinationRight(touchOffsetX, touchOffsetY);
}
else if (*touchOffsetX > 0.0f) // right
{
scrollenabled = checkCustomScrollDestinationLeft(touchOffsetX, touchOffsetY);
}
}
break;
}
default:
break;
}
return scrollenabled;
moveChildren(displacement.x, displacement.y);
}
bool ScrollView::processScrollUp(float* offsetYResult, float touchOffsetY)
@ -899,66 +743,23 @@ void ScrollView::jumpToPercentBothDirection(const Vec2& percent)
void ScrollView::startRecordSlidAction()
{
if (_autoScroll)
if (_inertiaScrolling)
{
stopAutoScrollChildren();
_inertiaScrolling = false;
}
if (_bouncingBack)
if(_autoScrolling)
{
_autoScrolling = false;
_bouncingBack = false;
}
_slidTime = 0.0f;
}
void ScrollView::endRecordSlidAction()
{
if (!startBounceBackIfNeeded() && _inertiaScrollEnabled)
bool bounceBackStarted = startBounceBackIfNeeded();
if(!bounceBackStarted && _inertiaScrollEnabled)
{
if (_slidTime <= 0.016f)
{
return;
}
float totalDis = 0.0f;
Vec2 dir;
Vec2 touchEndPositionInNodeSpace = this->convertToNodeSpace(_touchEndPosition);
Vec2 touchBeganPositionInNodeSpace = this->convertToNodeSpace(_touchBeganPosition);
switch (_direction)
{
case Direction::VERTICAL:
totalDis = touchEndPositionInNodeSpace.y - touchBeganPositionInNodeSpace.y;
if (totalDis < 0.0f)
{
dir = SCROLLDIR_DOWN;
}
else
{
dir = SCROLLDIR_UP;
}
break;
case Direction::HORIZONTAL:
totalDis = touchEndPositionInNodeSpace.x - touchBeganPositionInNodeSpace.x;
if (totalDis < 0.0f)
{
dir = SCROLLDIR_LEFT;
}
else
{
dir = SCROLLDIR_RIGHT;
}
break;
case Direction::BOTH:
{
Vec2 subVector = touchEndPositionInNodeSpace - touchBeganPositionInNodeSpace;
totalDis = subVector.getLength();
dir = subVector.getNormalized();
break;
}
default:
break;
}
float orSpeed = MIN(fabs(totalDis)/(_slidTime), AUTOSCROLLMAXSPEED);
startAutoScrollChildrenWithOriginalSpeed(dir, orSpeed, true, -1000);
_slidTime = 0.0f;
startInertiaScroll();
}
}
@ -966,6 +767,10 @@ void ScrollView::handlePressLogic(Touch *touch)
{
startRecordSlidAction();
_bePressed = true;
_inertiaPrevTouchTimestamp = getTimestamp();
_inertiaTouchDisplacements.clear();
_inertiaTouchTimeDeltas.clear();
}
void ScrollView::handleMoveLogic(Touch *touch)
@ -974,6 +779,17 @@ void ScrollView::handleMoveLogic(Touch *touch)
Vec2 previousTouchPositionInNodeSpace = this->convertToNodeSpace(touch->getPreviousLocation());
Vec2 delta = touchPositionInNodeSpace - previousTouchPositionInNodeSpace;
scrollChildren(delta.x, delta.y);
while(_inertiaTouchDisplacements.size() > 5)
{
_inertiaTouchDisplacements.pop_front();
_inertiaTouchTimeDeltas.pop_front();
}
_inertiaTouchDisplacements.push_back(delta);
long long timestamp = getTimestamp();
_inertiaTouchTimeDeltas.push_back((timestamp - _inertiaPrevTouchTimestamp) / 1000.0f);
_inertiaPrevTouchTimestamp = timestamp;
}
void ScrollView::handleReleaseLogic(Touch *touch)
@ -1026,22 +842,13 @@ void ScrollView::onTouchCancelled(Touch *touch, Event *unusedEvent)
void ScrollView::update(float dt)
{
if (_autoScroll)
if (_inertiaScrolling)
{
autoScrollChildren(dt);
processInertiaScrolling(dt);
}
if(_bouncingBack)
else if (_autoScrolling)
{
processBounceBack(dt);
}
recordSlidTime(dt);
}
void ScrollView::recordSlidTime(float dt)
{
if (_bePressed)
{
_slidTime += dt;
processAutoScrolling(dt);
}
}
@ -1085,7 +892,6 @@ void ScrollView::interceptTouchEvent(Widget::TouchEventType event, Widget *sende
void ScrollView::processScrollEvent(MoveDirection dir, bool bounce)
{
CCLOG("processScrollEvent() %d", dir);
ScrollviewEventType scrollEventType;
EventType eventType;
switch(dir) {

View File

@ -27,6 +27,7 @@ THE SOFTWARE.
#include "ui/UILayout.h"
#include "ui/GUIExport.h"
#include <list>
NS_CC_BEGIN
/**
@ -430,18 +431,18 @@ protected:
bool isOutOfBoundaryLeftOrRight() const;
void moveChildren(float offsetX, float offsetY);
void autoScrollChildren(float dt);
void startAutoScrollChildrenWithOriginalSpeed(const Vec2& dir, float v, bool attenuated, float acceleration);
void startInertiaScroll();
void processInertiaScrolling(float dt);
void startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated);
void startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated);
void jumpToDestination(const Vec2& des);
void stopAutoScrollChildren();
void processAutoScrolling(float deltaTime);
bool startBounceBackIfNeeded();
void processBounceBack(float deltaTime);
bool checkCustomScrollDestinationLeft(float* touchOffsetX, float* touchOffsetY);
bool checkCustomScrollDestinationRight(float* touchOffsetX, float* touchOffsetY);
bool checkCustomScrollDestination(float* touchOffsetX, float* touchOffsetY);
void jumpToDestination(const Vec2& des);
virtual bool scrollChildren(float touchOffsetX, float touchOffsetY);
@ -461,8 +462,6 @@ protected:
virtual void interceptTouchEvent(Widget::TouchEventType event,Widget* sender,Touch *touch) override;
void recordSlidTime(float dt);
void processScrollEvent(MoveDirection dir, bool bounce);
void processScrollingEvent();
void dispatchEvent(ScrollviewEventType scrollEventType, EventType eventType);
@ -473,35 +472,33 @@ protected:
Layout* _innerContainer;
Direction _direction;
Vec2 _autoScrollDir;
float _topBoundary;
float _bottomBoundary;
float _leftBoundary;
float _rightBoundary;
bool _autoScroll;
float _autoScrollAddUpTime;
float _autoScrollOriginalSpeed;
float _autoScrollAcceleration;
bool _isAutoScrollSpeedAttenuated;
bool _needCheckAutoScrollDestination;
Vec2 _autoScrollDestination;
bool _bePressed;
float _slidTime;
float _childFocusCancelOffset;
bool _inertiaScrollEnabled;
bool _inertiaScrolling;
Vec2 _inertiaInitiVelocity;
std::list<Vec2> _inertiaTouchDisplacements;
std::list<float> _inertiaTouchTimeDeltas;
long long _inertiaPrevTouchTimestamp;
float _inertiaScrollExpectedTime;
float _inertiaScrollElapsedTime;
bool _autoScrolling;
bool _autoScrollAttenuate;
Vec2 _autoScrollStartPosition;
Vec2 _autoScrollTargetDelta;
float _autoScrollDuration;
float _autoScrollAccumulatedTime;
bool _bounceEnabled;
bool _bouncingBack;
bool _bounceBackAttenuate;
Vec2 _bounceBackStartPosition;
Vec2 _bounceBackTargetDelta;
float _bounceBackDuration;
float _bounceBackAccumulatedTime;
bool _inertiaScrollEnabled;
Ref* _scrollViewEventListener;
#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))