Merge pull request #13723 from neokim/add_scroll_to_item_and_magnetic_scroll_into_listview

Add scrolling to item and magnetic scroll into ListView
This commit is contained in:
子龙山人 2015-09-11 17:48:40 +08:00
commit 96d6221daf
6 changed files with 1063 additions and 254 deletions

View File

@ -27,6 +27,8 @@ THE SOFTWARE.
NS_CC_BEGIN
static const float DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM = 1.0f;
namespace ui {
IMPLEMENT_CLASS_GUI_INFO(ListView)
@ -34,6 +36,8 @@ IMPLEMENT_CLASS_GUI_INFO(ListView)
ListView::ListView():
_model(nullptr),
_gravity(Gravity::CENTER_VERTICAL),
_magneticType(MagneticType::NONE),
_magneticAllowedOutOfBoundary(true),
_itemsMargin(0.0f),
_curSelectedIndex(-1),
_refreshViewDirty(true),
@ -86,6 +90,16 @@ void ListView::setItemModel(Widget *model)
CC_SAFE_RETAIN(_model);
}
void ListView::handleReleaseLogic(Touch *touch)
{
ScrollView::handleReleaseLogic(touch);
if(!_autoScrolling)
{
startMagneticScroll();
}
}
void ListView::updateInnerContainerSize()
{
switch (_direction)
@ -249,6 +263,7 @@ void ListView::addChild(cocos2d::Node *child, int zOrder, int tag)
if (nullptr != widget)
{
_items.pushBack(widget);
_outOfBoundaryAmountDirty = true;
}
}
@ -270,6 +285,7 @@ void ListView::addChild(Node* child, int zOrder, const std::string &name)
if (nullptr != widget)
{
_items.pushBack(widget);
_outOfBoundaryAmountDirty = true;
}
}
@ -291,6 +307,7 @@ void ListView::removeChild(cocos2d::Node *child, bool cleaup)
}
}
_items.eraseObject(widget);
_outOfBoundaryAmountDirty = true;
}
ScrollView::removeChild(child, cleaup);
@ -306,6 +323,7 @@ void ListView::removeAllChildrenWithCleanup(bool cleanup)
ScrollView::removeAllChildrenWithCleanup(cleanup);
_items.clear();
_curSelectedIndex = -1;
_outOfBoundaryAmountDirty = true;
}
void ListView::insertCustomItem(Widget* item, ssize_t index)
@ -318,6 +336,8 @@ void ListView::insertCustomItem(Widget* item, ssize_t index)
}
}
_items.insert(index, item);
_outOfBoundaryAmountDirty = true;
ScrollView::addChild(item);
remedyLayoutParameter(item);
@ -379,6 +399,28 @@ void ListView::setGravity(Gravity gravity)
_refreshViewDirty = true;
}
void ListView::setMagneticType(MagneticType magneticType)
{
_magneticType = magneticType;
_outOfBoundaryAmountDirty = true;
startMagneticScroll();
}
ListView::MagneticType ListView::getMagneticType() const
{
return _magneticType;
}
void ListView::setMagneticAllowedOutOfBoundary(bool magneticAllowedOutOfBoundary)
{
_magneticAllowedOutOfBoundary = magneticAllowedOutOfBoundary;
}
bool ListView::getMagneticAllowedOutOfBoundary() const
{
return _magneticAllowedOutOfBoundary;
}
void ListView::setItemsMargin(float margin)
{
if (_itemsMargin == margin)
@ -529,6 +571,142 @@ void ListView::interceptTouchEvent(TouchEventType event, Widget *sender, Touch*
}
}
static Vec2 calculateItemPositionWithAnchor(Widget* item, const Vec2& itemAnchorPoint)
{
Vec2 origin(item->getLeftBoundary(), item->getBottomBoundary());
Size size = item->getContentSize();
return origin + Vec2(size.width * itemAnchorPoint.x, size.height * itemAnchorPoint.y);
}
static Widget* findClosestItem(const Vec2& targetPosition, const Vector<Widget*>& items, const Vec2& itemAnchorPoint, ssize_t firstIndex, float distanceFromFirst, ssize_t lastIndex, float distanceFromLast)
{
CCASSERT(firstIndex >= 0 && lastIndex < items.size() && firstIndex <= lastIndex, "");
if (firstIndex == lastIndex)
{
return items.at(firstIndex);
}
if (lastIndex - firstIndex == 1)
{
if (distanceFromFirst <= distanceFromLast)
{
return items.at(firstIndex);
}
else
{
return items.at(lastIndex);
}
}
// Binary search
ssize_t midIndex = (firstIndex + lastIndex) / 2;
Vec2 itemPosition = calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint);
float distanceFromMid = (targetPosition - itemPosition).length();
if (distanceFromFirst <= distanceFromLast)
{
// Left half
return findClosestItem(targetPosition, items, itemAnchorPoint, firstIndex, distanceFromFirst, midIndex, distanceFromMid);
}
else
{
// Right half
return findClosestItem(targetPosition, items, itemAnchorPoint, midIndex, distanceFromMid, lastIndex, distanceFromLast);
}
}
Widget* ListView::getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint) const
{
if (_items.empty())
{
return nullptr;
}
// Find the closest item through binary search
ssize_t firstIndex = 0;
Vec2 firstPosition = calculateItemPositionWithAnchor(_items.at(firstIndex), itemAnchorPoint);
float distanceFromFirst = (targetPosition - firstPosition).length();
ssize_t lastIndex = _items.size() - 1;
Vec2 lastPosition = calculateItemPositionWithAnchor(_items.at(lastIndex), itemAnchorPoint);
float distanceFromLast = (targetPosition - lastPosition).length();
return findClosestItem(targetPosition, _items, itemAnchorPoint, firstIndex, distanceFromFirst, lastIndex, distanceFromLast);
}
Widget* ListView::getClosestItemToPositionInCurrentView(const Vec2& positionRatioInView, const Vec2& itemAnchorPoint) const
{
// Calculate the target position
Size contentSize = getContentSize();
Vec2 targetPosition = -_innerContainer->getPosition();
targetPosition.x += contentSize.width * positionRatioInView.x;
targetPosition.y += contentSize.height * positionRatioInView.y;
return getClosestItemToPosition(targetPosition, itemAnchorPoint);
}
Widget* ListView::getCenterItemInCurrentView() const
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE, Vec2::ANCHOR_MIDDLE);
}
Widget* ListView::getLeftmostItemInCurrentView() const
{
if (_direction == Direction::HORIZONTAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_LEFT, Vec2::ANCHOR_MIDDLE);
}
return nullptr;
}
Widget* ListView::getRightmostItemInCurrentView() const
{
if (_direction == Direction::HORIZONTAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_RIGHT, Vec2::ANCHOR_MIDDLE);
}
return nullptr;
}
Widget* ListView::getTopmostItemInCurrentView() const
{
if (_direction == Direction::VERTICAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_TOP, Vec2::ANCHOR_MIDDLE);
}
return nullptr;
}
Widget* ListView::getBottommostItemInCurrentView() const
{
if (_direction == Direction::VERTICAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_BOTTOM, Vec2::ANCHOR_MIDDLE);
}
return nullptr;
}
void ListView::scrollToItem(int itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint)
{
scrollToItem(itemIndex, positionRatioInView, itemAnchorPoint, DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM);
}
void ListView::scrollToItem(int itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec)
{
Widget* item = getItem(itemIndex);
if (item == nullptr)
{
return;
}
Size contentSize = getContentSize();
Vec2 positionInView;
positionInView.x += contentSize.width * positionRatioInView.x;
positionInView.y += contentSize.height * positionRatioInView.y;
Vec2 itemPosition = calculateItemPositionWithAnchor(item, itemAnchorPoint);
Vec2 destination = -(itemPosition - positionInView);
startAutoScrollToDestination(destination, timeInSec, true);
}
ssize_t ListView::getCurSelectedIndex() const
{
return _curSelectedIndex;
@ -574,5 +752,154 @@ void ListView::copySpecialProperties(Widget *widget)
}
}
Vec2 ListView::getHowMuchOutOfBoundary(const Vec2& addition)
{
if(!_magneticAllowedOutOfBoundary || _items.empty())
{
return ScrollView::getHowMuchOutOfBoundary(addition);
}
else if(_magneticType == MagneticType::NONE || _magneticType == MagneticType::BOTH_END)
{
return ScrollView::getHowMuchOutOfBoundary(addition);
}
else if(addition == Vec2::ZERO && !_outOfBoundaryAmountDirty)
{
return _outOfBoundaryAmount;
}
// If it is allowed to be out of boundary by magnetic, adjust the boundaries according to the magnetic type.
float leftBoundary = _leftBoundary;
float rightBoundary = _rightBoundary;
float topBoundary = _topBoundary;
float bottomBoundary = _bottomBoundary;
{
int lastItemIndex = _items.size() - 1;
Size contentSize = getContentSize();
Vec2 firstItemAdjustment, lastItemAdjustment;
if(_magneticType == MagneticType::CENTER)
{
firstItemAdjustment = (contentSize - _items.at(0)->getContentSize()) / 2;
lastItemAdjustment = (contentSize - _items.at(lastItemIndex)->getContentSize()) / 2;
}
else if(_magneticType == MagneticType::LEFT)
{
lastItemAdjustment = contentSize - _items.at(lastItemIndex)->getContentSize();
}
else if(_magneticType == MagneticType::RIGHT)
{
firstItemAdjustment = contentSize - _items.at(0)->getContentSize();
}
else if(_magneticType == MagneticType::TOP)
{
lastItemAdjustment = contentSize - _items.at(lastItemIndex)->getContentSize();
}
else if(_magneticType == MagneticType::BOTTOM)
{
firstItemAdjustment = contentSize - _items.at(0)->getContentSize();
}
leftBoundary += firstItemAdjustment.x;
rightBoundary -= lastItemAdjustment.x;
topBoundary -= firstItemAdjustment.y;
bottomBoundary += lastItemAdjustment.y;
}
// Calculate the actual amount
Vec2 outOfBoundaryAmount;
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;
}
static Vec2 getAnchorPointByMagneticType(ListView::MagneticType magneticType)
{
switch(magneticType)
{
case ListView::MagneticType::NONE: return Vec2::ZERO;
case ListView::MagneticType::BOTH_END: return Vec2::ANCHOR_TOP_LEFT;
case ListView::MagneticType::CENTER: return Vec2::ANCHOR_MIDDLE;
case ListView::MagneticType::LEFT: return Vec2::ANCHOR_MIDDLE_LEFT;
case ListView::MagneticType::RIGHT: return Vec2::ANCHOR_MIDDLE_RIGHT;
case ListView::MagneticType::TOP: return Vec2::ANCHOR_MIDDLE_TOP;
case ListView::MagneticType::BOTTOM: return Vec2::ANCHOR_MIDDLE_BOTTOM;
}
return Vec2::ZERO;
}
void ListView::startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity)
{
Vec2 adjustedDeltaMove = deltaMove;
if(!_items.empty() && _magneticType != MagneticType::NONE)
{
adjustedDeltaMove = flattenVectorByDirection(adjustedDeltaMove);
// If the destination is out of boundary, do nothing here. Because it will be handled by bouncing back.
if(getHowMuchOutOfBoundary(adjustedDeltaMove) == Vec2::ZERO)
{
MagneticType magType = _magneticType;
if(magType == MagneticType::BOTH_END)
{
if(_direction == Direction::HORIZONTAL)
{
magType = (adjustedDeltaMove.x > 0 ? MagneticType::LEFT : MagneticType::RIGHT);
}
else if(_direction == Direction::VERTICAL)
{
magType = (adjustedDeltaMove.y > 0 ? MagneticType::BOTTOM : MagneticType::TOP);
}
}
// Adjust the delta move amount according to the magnetic type
Vec2 magneticAnchorPoint = getAnchorPointByMagneticType(magType);
Vec2 magneticPosition = -_innerContainer->getPosition();
magneticPosition.x += getContentSize().width * magneticAnchorPoint.x;
magneticPosition.y += getContentSize().height * magneticAnchorPoint.y;
Widget* pTargetItem = getClosestItemToPosition(magneticPosition - adjustedDeltaMove, magneticAnchorPoint);
Vec2 itemPosition = calculateItemPositionWithAnchor(pTargetItem, magneticAnchorPoint);
adjustedDeltaMove = magneticPosition - itemPosition;
}
}
ScrollView::startAttenuatingAutoScroll(adjustedDeltaMove, initialVelocity);
}
void ListView::startMagneticScroll()
{
if(_items.empty() || _magneticType == MagneticType::NONE)
{
return;
}
// Find the closest item
Vec2 magneticAnchorPoint = getAnchorPointByMagneticType(_magneticType);
Vec2 magneticPosition = -_innerContainer->getPosition();
magneticPosition.x += getContentSize().width * magneticAnchorPoint.x;
magneticPosition.y += getContentSize().height * magneticAnchorPoint.y;
Widget* pTargetItem = getClosestItemToPosition(magneticPosition, magneticAnchorPoint);
scrollToItem(getIndex(pTargetItem), magneticAnchorPoint, magneticAnchorPoint);
}
}
NS_CC_END

View File

@ -86,6 +86,22 @@ public:
ON_SELECTED_ITEM_END
};
/**
* ListView supports magnetic scroll.
* With CENTER type, ListView tries to align its items in center of current view.
* With BOTH_END type, ListView tries to align its items in left or right end if it is horizontal, top or bottom in vertical. The aligning side (left or right, top or bottom) is determined by user's scroll direction.
*/
enum class MagneticType
{
NONE,
CENTER,
BOTH_END,
LEFT,
RIGHT,
TOP,
BOTTOM,
};
/**
* ListView item click callback.
*/
@ -193,6 +209,27 @@ public:
*/
void setGravity(Gravity gravity);
/**
* Set magnetic type of ListView.
* @see `MagneticType`
*/
void setMagneticType(MagneticType magneticType);
/**
* Get magnetic type of ListView.
*/
MagneticType getMagneticType() const;
/**
* Set magnetic allowed out of boundary.
*/
void setMagneticAllowedOutOfBoundary(bool magneticAllowedOutOfBoundary);
/**
* Query whether the magnetic out of boundary is allowed.
*/
bool getMagneticAllowedOutOfBoundary() const;
/**
* Set the margin between each item in ListView.
*
@ -200,7 +237,6 @@ public:
*/
void setItemsMargin(float margin);
/**
* @brief Query margin between each item in ListView.
*
@ -220,7 +256,64 @@ public:
virtual void removeAllChildrenWithCleanup(bool cleanup) override;
virtual void removeChild(Node* child, bool cleaup = true) override;
/**
* @brief Query the closest item to a specific position in inner container.
*
* @param targetPosition Specifies the target position in inner container's coordinates.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @return A item instance if list view is not empty. Otherwise, returns null.
*/
Widget* getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint) const;
/**
* @brief Query the closest item to a specific position in current view.
* For instance, to find the item in the center of view, call 'getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE, Vec2::ANCHOR_MIDDLE)'.
*
* @param positionRatioInView Specifies the target position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @return A item instance if list view is not empty. Otherwise, returns null.
*/
Widget* getClosestItemToPositionInCurrentView(const Vec2& positionRatioInView, const Vec2& itemAnchorPoint) const;
/**
* @brief Query the center item
* @return A item instance.
*/
Widget* getCenterItemInCurrentView() const;
/**
* @brief Query the leftmost item in horizontal list
* @return A item instance.
*/
Widget* getLeftmostItemInCurrentView() const;
/**
* @brief Query the rightmost item in horizontal list
* @return A item instance.
*/
Widget* getRightmostItemInCurrentView() const;
/**
* @brief Query the topmost item in horizontal list
* @return A item instance.
*/
Widget* getTopmostItemInCurrentView() const;
/**
* @brief Query the bottommost item in horizontal list
* @return A item instance.
*/
Widget* getBottommostItemInCurrentView() const;
/**
* @brief Scroll to specific item
* @param positionRatioInView Specifies the position with ratio in list view's content size.
* @param itemAnchorPoint Specifies an anchor point of each item for position to calculate distance.
* @param timeInSec Scroll time
*/
void scrollToItem(int itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint);
void scrollToItem(int itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec);
/**
* @brief Query current selected widget's idnex.
*
@ -270,6 +363,7 @@ CC_CONSTRUCTOR_ACCESS:
virtual bool init() override;
protected:
virtual void handleReleaseLogic(Touch *touch) override;
void updateInnerContainerSize();
void remedyLayoutParameter(Widget* item);
@ -282,6 +376,13 @@ protected:
virtual void copyClonedWidgetChildren(Widget* model) override;
void selectedItemEvent(TouchEventType event);
virtual void interceptTouchEvent(Widget::TouchEventType event,Widget* sender,Touch* touch) override;
virtual Vec2 getHowMuchOutOfBoundary(const Vec2& addition = Vec2::ZERO) override;
virtual void startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity) override;
void startMagneticScroll();
protected:
Widget* _model;
@ -289,6 +390,9 @@ protected:
Gravity _gravity;
MagneticType _magneticType;
bool _magneticAllowedOutOfBoundary;
float _itemsMargin;
ssize_t _curSelectedIndex;

View File

@ -31,8 +31,8 @@ THE SOFTWARE.
#include "2d/CCCamera.h"
NS_CC_BEGIN
static const float OUT_OF_BOUND_TIME_RESCALE = 0.05f;
static const float OUT_OF_BOUND_DISTANCE_RESCALE = 0.05f;
static const int NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED = 5;
static const float OUT_OF_BOUNDARY_BREAKING_FACTOR = 0.05f;
static const float BOUNCE_BACK_DURATION = 1.0f;
#define MOVE_INCH 7.0f/160.0f
@ -61,14 +61,13 @@ _childFocusCancelOffsetInInch(MOVE_INCH),
_touchMovePreviousTimestamp(0),
_autoScrolling(false),
_autoScrollAttenuate(true),
_autoScrollDuration(0),
_autoScrollTotalTime(0),
_autoScrollAccumulatedTime(0),
_autoScrollCompleteCallback(nullptr),
_autoScrollMoveCallback(nullptr),
_outOfBoundaryDuringInertiaScroll(false),
_autoScrollCurrentlyOutOfBoundary(false),
_autoScrollBraking(false),
_inertiaScrollEnabled(true),
_inertiaScrolling(false),
_bounceEnabled(false),
_outOfBoundaryAmountDirty(true),
_scrollBarEnabled(true),
_verticalScrollBar(nullptr),
_horizontalScrollBar(nullptr),
@ -229,6 +228,19 @@ void ScrollView::setInnerContainerPosition(const Vec2 &position)
return;
}
_innerContainer->setPosition(position);
_outOfBoundaryAmountDirty = true;
// Process bouncing events
if(_bounceEnabled)
{
for(int direction = (int) MoveDirection::TOP; direction < (int) MoveDirection::RIGHT; ++direction)
{
if(isOutOfBoundary((MoveDirection) direction))
{
processScrollEvent((MoveDirection) direction, true);
}
}
}
this->retain();
if (_eventCallback)
@ -333,178 +345,222 @@ void ScrollView::updateScrollBar(const Vec2& outOfBoundary)
}
}
Vec2 ScrollView::calculateTouchMoveVelocity() const
{
float totalTime = 0;
for(auto &timeDelta : _touchMoveTimeDeltas)
{
totalTime += timeDelta;
}
if(totalTime == 0 || totalTime >= 0.1f)
{
return Vec2::ZERO;
}
Vec2 totalMovement;
for(auto &displacement : _touchMoveDisplacements)
{
totalMovement += displacement;
}
return totalMovement / totalTime;
}
void ScrollView::startInertiaScroll(const Vec2& touchMoveVelocity)
{
const float MOVEMENT_FACTOR = 0.7f;
Vec2 inertiaTotalMovement = touchMoveVelocity * MOVEMENT_FACTOR;
startAttenuatingAutoScroll(inertiaTotalMovement, touchMoveVelocity);
}
bool ScrollView::startBounceBackIfNeeded()
{
if (!_bounceEnabled)
{
return false;
}
Vec2 outOfBoundary = getHowMuchOutOfBoundary(Vec2::ZERO);
if(outOfBoundary == Vec2::ZERO)
Vec2 bounceBackAmount = getHowMuchOutOfBoundary();
if(bounceBackAmount == Vec2::ZERO)
{
return false;
}
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);
}
}
);
startAutoScroll(bounceBackAmount, BOUNCE_BACK_DURATION, true);
return true;
}
Vec2 ScrollView::getHowMuchOutOfBoundary(const Vec2& addition) const
Vec2 ScrollView::flattenVectorByDirection(const Vec2& vector)
{
Vec2 result;
if(_innerContainer->getLeftBoundary() + addition.x > _leftBoundary)
{
result.x = _leftBoundary - (_innerContainer->getLeftBoundary() + addition.x);
}
else if(_innerContainer->getRightBoundary() + addition.x < _rightBoundary)
{
result.x = _rightBoundary - (_innerContainer->getRightBoundary() + addition.x);
}
if(_innerContainer->getTopBoundary() + addition.y < _topBoundary)
{
result.y = _topBoundary - (_innerContainer->getTopBoundary() + addition.y);
}
else if(_innerContainer->getBottomBoundary() + addition.y > _bottomBoundary)
{
result.y = _bottomBoundary - (_innerContainer->getBottomBoundary() + addition.y);
}
Vec2 result = vector;
result.x = (_direction == Direction::VERTICAL ? 0 : result.x);
result.y = (_direction == Direction::HORIZONTAL ? 0 : result.y);
return result;
}
bool ScrollView::isOutOfBoundary(MoveDirection dir) const
Vec2 ScrollView::getHowMuchOutOfBoundary(const Vec2& addition)
{
if(addition == Vec2::ZERO && !_outOfBoundaryAmountDirty)
{
return _outOfBoundaryAmount;
}
Vec2 outOfBoundaryAmount;
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;
}
bool ScrollView::isOutOfBoundary(MoveDirection dir)
{
Vec2 outOfBoundary = getHowMuchOutOfBoundary();
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 outOfBoundary.y > 0;
case MoveDirection::BOTTOM: return outOfBoundary.y < 0;
case MoveDirection::LEFT: return outOfBoundary.x < 0;
case MoveDirection::RIGHT: return outOfBoundary.x > 0;
}
return false;
}
bool ScrollView::isOutOfBoundaryTopOrBottom() const
bool ScrollView::isOutOfBoundary()
{
return isOutOfBoundary(MoveDirection::TOP) || isOutOfBoundary(MoveDirection::BOTTOM);
return getHowMuchOutOfBoundary() != Vec2::ZERO;
}
bool ScrollView::isOutOfBoundaryLeftOrRight() const
void ScrollView::startAutoScrollToDestination(const Vec2& destination, float timeInSec, bool attenuated)
{
return isOutOfBoundary(MoveDirection::LEFT) || isOutOfBoundary(MoveDirection::RIGHT);
startAutoScroll(destination - _innerContainer->getPosition(), timeInSec, attenuated);
}
bool ScrollView::isOutOfBoundary() const
static float calculateAutoScrollTimeByInitialSpeed(float initialSpeed)
{
return isOutOfBoundaryTopOrBottom() || isOutOfBoundaryLeftOrRight();
// Calculate the time from the initial speed according to quintic polynomial.
float time = sqrtf(sqrtf(initialSpeed / 5));
return time;
}
void ScrollView::startAutoScrollChildrenWithDestination(const Vec2& des, float second, bool attenuated)
void ScrollView::startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity)
{
startAutoScroll(des - _innerContainer->getPosition(), second, attenuated);
float time = calculateAutoScrollTimeByInitialSpeed(initialVelocity.length());
startAutoScroll(deltaMove, time, true);
}
void ScrollView::startAutoScroll(const Vec2& deltaMove, float duration, bool attenuated, std::function<void()> completeCallback, std::function<void(const Vec2&)> moveCallback)
void ScrollView::startAutoScroll(const Vec2& deltaMove, float timeInSec, bool attenuated)
{
Vec2 adjustedDeltaMove = flattenVectorByDirection(deltaMove);
_autoScrolling = true;
_autoScrollTargetDelta = deltaMove;
_autoScrollTargetDelta = adjustedDeltaMove;
_autoScrollAttenuate = attenuated;
_autoScrollStartPosition = _innerContainer->getPosition();
_autoScrollDuration = duration;
_autoScrollTotalTime = timeInSec;
_autoScrollAccumulatedTime = 0;
_autoScrollCompleteCallback = completeCallback;
_autoScrollMoveCallback = moveCallback;
_outOfBoundaryDuringInertiaScroll = false;
_outOfBoundaryPositionDuringInertiaScroll = Vec2::ZERO;
_autoScrollBraking = false;
_autoScrollBrakingStartPosition = Vec2::ZERO;
// If the destination is also out of boundary of same side, start brake from beggining.
Vec2 currentOutOfBoundary = getHowMuchOutOfBoundary();
if(currentOutOfBoundary != Vec2::ZERO)
{
_autoScrollCurrentlyOutOfBoundary = true;
Vec2 afterOutOfBoundary = getHowMuchOutOfBoundary(adjustedDeltaMove);
if(currentOutOfBoundary.x * afterOutOfBoundary.x > 0 || currentOutOfBoundary.y * afterOutOfBoundary.y > 0)
{
_autoScrollBraking = true;
}
}
}
void ScrollView::processAutoScrolling(float deltaTime)
bool ScrollView::isNecessaryAutoScrollBrake()
{
// Shorten the auto scroll distance and time if it is out of boundary during inertia scroll.
float timeRescale = 1;
float distanceRescale = 1;
if(_autoScrollBraking)
{
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);
return true;
}
// Calculate the percentage
float percentage = 0;
if(isOutOfBoundary())
{
_autoScrollAccumulatedTime += deltaTime * (1 / timeRescale);
percentage = MIN(1, _autoScrollAccumulatedTime / _autoScrollDuration);
if(_autoScrollAttenuate)
// It just went out of boundary.
if(!_autoScrollCurrentlyOutOfBoundary)
{
// Use quintic(5th degree) polynomial
percentage = tweenfunc::quintEaseOut(percentage);
_autoScrollCurrentlyOutOfBoundary = true;
_autoScrollBraking = true;
_autoScrollBrakingStartPosition = getInnerContainerPosition();
return true;
}
}
else
{
_autoScrollCurrentlyOutOfBoundary = false;
}
return false;
}
void ScrollView::processAutoScrolling(float deltaTime)
{
// Make auto scroll shorter if it needs to deaccelerate.
float brakingFactor = (isNecessaryAutoScrollBrake() ? OUT_OF_BOUNDARY_BREAKING_FACTOR : 1);
// Elapsed time
_autoScrollAccumulatedTime += deltaTime * (1 / brakingFactor);
// Calculate the progress percentage
float percentage = MIN(1, _autoScrollAccumulatedTime / _autoScrollTotalTime);
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;
Vec2 newPosition = _autoScrollStartPosition + (_autoScrollTargetDelta * percentage);
bool reachedEnd = (percentage == 1);
// Adjust the new position according to the bounce opiton
if(_bounceEnabled)
{
if(_bounceEnabled)
// 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 - getInnerContainerPosition();
Vec2 outOfBoundary = getHowMuchOutOfBoundary(moveDelta);
if(outOfBoundary != Vec2::ZERO)
{
newPosition = _outOfBoundaryPositionDuringInertiaScroll + (newPosition - _outOfBoundaryPositionDuringInertiaScroll) * distanceRescale;
}
else
{
Vec2 moveDelta = newPosition - getInnerContainerPosition();
Vec2 outOfBoundary = getHowMuchOutOfBoundary(moveDelta);
if(outOfBoundary != Vec2::ZERO)
{
newPosition += outOfBoundary;
reachedEnd = true;
}
newPosition += outOfBoundary;
reachedEnd = true;
}
}
moveChildrenToPosition(newPosition);
if(_autoScrollMoveCallback)
{
_autoScrollMoveCallback(getInnerContainerPosition() - newPosition);
}
// Finish auto scroll if it ended
if(percentage == 1 || reachedEnd)
if(reachedEnd)
{
_autoScrolling = false;
if(_autoScrollCompleteCallback)
{
_autoScrollCompleteCallback();
}
startBounceBackIfNeeded();
}
}
@ -523,45 +579,6 @@ void ScrollView::jumpToDestination(const Vec2 &des)
moveChildrenToPosition(Vec2(finalOffsetX, finalOffsetY));
}
Vec2 ScrollView::calculateTouchMoveVelocity() const
{
float totalDuration = 0;
for(auto &timeDelta : _touchMoveTimeDeltas)
{
totalDuration += timeDelta;
}
if(totalDuration == 0 || totalDuration >= 0.5f)
{
return Vec2::ZERO;
}
Vec2 totalMovement;
for(auto &displacement : _touchMoveDisplacements)
{
totalMovement += displacement;
}
return totalMovement / totalDuration;
}
void ScrollView::startInertiaScroll(const Vec2& touchMoveVelocity)
{
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();
});
}
bool ScrollView::scrollChildren(float touchOffsetX, float touchOffsetY)
{
touchOffsetX = (_direction == Direction::VERTICAL ? 0 : touchOffsetX);
@ -569,8 +586,9 @@ bool ScrollView::scrollChildren(float touchOffsetX, float touchOffsetY)
if(_bounceEnabled)
{
// If the position of the inner container is out of the boundary, the offsets should be divided by two.
touchOffsetX *= (isOutOfBoundaryLeftOrRight() ? 0.5f : 1);
touchOffsetY *= (isOutOfBoundaryTopOrBottom() ? 0.5f : 1);
Vec2 outOfBoundary = getHowMuchOutOfBoundary();
touchOffsetX *= (outOfBoundary.x == 0 ? 1 : 0.5f);
touchOffsetY *= (outOfBoundary.y == 0 ? 1 : 0.5f);
}
float realOffsetX = touchOffsetX;
@ -657,83 +675,83 @@ bool ScrollView::scrollChildren(float touchOffsetX, float touchOffsetY)
return scrollEnabledUpDown || scrollEnabledLeftRight;
}
void ScrollView::scrollToBottom(float second, bool attenuated)
void ScrollView::scrollToBottom(float timeInSec, bool attenuated)
{
startAutoScrollChildrenWithDestination(Vec2(_innerContainer->getPosition().x, 0.0f), second, attenuated);
startAutoScrollToDestination(Vec2(_innerContainer->getPosition().x, 0.0f), timeInSec, attenuated);
}
void ScrollView::scrollToTop(float second, bool attenuated)
void ScrollView::scrollToTop(float timeInSec, bool attenuated)
{
startAutoScrollChildrenWithDestination(Vec2(_innerContainer->getPosition().x,
_contentSize.height - _innerContainer->getContentSize().height), second, attenuated);
startAutoScrollToDestination(Vec2(_innerContainer->getPosition().x,
_contentSize.height - _innerContainer->getContentSize().height), timeInSec, attenuated);
}
void ScrollView::scrollToLeft(float second, bool attenuated)
void ScrollView::scrollToLeft(float timeInSec, bool attenuated)
{
startAutoScrollChildrenWithDestination(Vec2(0.0f, _innerContainer->getPosition().y), second, attenuated);
startAutoScrollToDestination(Vec2(0.0f, _innerContainer->getPosition().y), timeInSec, attenuated);
}
void ScrollView::scrollToRight(float second, bool attenuated)
void ScrollView::scrollToRight(float timeInSec, bool attenuated)
{
startAutoScrollChildrenWithDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width,
_innerContainer->getPosition().y), second, attenuated);
startAutoScrollToDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width,
_innerContainer->getPosition().y), timeInSec, attenuated);
}
void ScrollView::scrollToTopLeft(float second, bool attenuated)
void ScrollView::scrollToTopLeft(float timeInSec, bool attenuated)
{
if (_direction != Direction::BOTH)
{
CCLOG("Scroll direction is not both!");
return;
}
startAutoScrollChildrenWithDestination(Vec2(0.0f, _contentSize.height - _innerContainer->getContentSize().height), second, attenuated);
startAutoScrollToDestination(Vec2(0.0f, _contentSize.height - _innerContainer->getContentSize().height), timeInSec, attenuated);
}
void ScrollView::scrollToTopRight(float second, bool attenuated)
void ScrollView::scrollToTopRight(float timeInSec, bool attenuated)
{
if (_direction != Direction::BOTH)
{
CCLOG("Scroll direction is not both!");
return;
}
startAutoScrollChildrenWithDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width,
_contentSize.height - _innerContainer->getContentSize().height), second, attenuated);
startAutoScrollToDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width,
_contentSize.height - _innerContainer->getContentSize().height), timeInSec, attenuated);
}
void ScrollView::scrollToBottomLeft(float second, bool attenuated)
void ScrollView::scrollToBottomLeft(float timeInSec, bool attenuated)
{
if (_direction != Direction::BOTH)
{
CCLOG("Scroll direction is not both!");
return;
}
startAutoScrollChildrenWithDestination(Vec2::ZERO, second, attenuated);
startAutoScrollToDestination(Vec2::ZERO, timeInSec, attenuated);
}
void ScrollView::scrollToBottomRight(float second, bool attenuated)
void ScrollView::scrollToBottomRight(float timeInSec, bool attenuated)
{
if (_direction != Direction::BOTH)
{
CCLOG("Scroll direction is not both!");
return;
}
startAutoScrollChildrenWithDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width, 0.0f), second, attenuated);
startAutoScrollToDestination(Vec2(_contentSize.width - _innerContainer->getContentSize().width, 0.0f), timeInSec, attenuated);
}
void ScrollView::scrollToPercentVertical(float percent, float second, bool attenuated)
void ScrollView::scrollToPercentVertical(float percent, float timeInSec, bool attenuated)
{
float minY = _contentSize.height - _innerContainer->getContentSize().height;
float h = - minY;
startAutoScrollChildrenWithDestination(Vec2(_innerContainer->getPosition().x, minY + percent * h / 100.0f), second, attenuated);
startAutoScrollToDestination(Vec2(_innerContainer->getPosition().x, minY + percent * h / 100.0f), timeInSec, attenuated);
}
void ScrollView::scrollToPercentHorizontal(float percent, float second, bool attenuated)
void ScrollView::scrollToPercentHorizontal(float percent, float timeInSec, bool attenuated)
{
float w = _innerContainer->getContentSize().width - _contentSize.width;
startAutoScrollChildrenWithDestination(Vec2(-(percent * w / 100.0f), _innerContainer->getPosition().y), second, attenuated);
startAutoScrollToDestination(Vec2(-(percent * w / 100.0f), _innerContainer->getPosition().y), timeInSec, attenuated);
}
void ScrollView::scrollToPercentBothDirection(const Vec2& percent, float second, bool attenuated)
void ScrollView::scrollToPercentBothDirection(const Vec2& percent, float timeInSec, bool attenuated)
{
if (_direction != Direction::BOTH)
{
@ -742,7 +760,7 @@ void ScrollView::scrollToPercentBothDirection(const Vec2& percent, float second,
float minY = _contentSize.height - _innerContainer->getContentSize().height;
float h = - minY;
float w = _innerContainer->getContentSize().width - _contentSize.width;
startAutoScrollChildrenWithDestination(Vec2(-(percent.x * w / 100.0f), minY + percent.y * h / 100.0f), second, attenuated);
startAutoScrollToDestination(Vec2(-(percent.x * w / 100.0f), minY + percent.y * h / 100.0f), timeInSec, attenuated);
}
void ScrollView::jumpToBottom()
@ -832,13 +850,37 @@ void ScrollView::jumpToPercentBothDirection(const Vec2& percent)
jumpToDestination(Vec2(-(percent.x * w / 100.0f), minY + percent.y * h / 100.0f));
}
bool ScrollView::calculateCurrAndPrevTouchPoints(Touch* touch, Vec3* currPt, Vec3* prevPt)
{
if (nullptr == _hittedByCamera ||
false == hitTest(touch->getLocation(), _hittedByCamera, currPt) ||
false == hitTest(touch->getPreviousLocation(), _hittedByCamera, prevPt))
{
return false;
}
return true;
}
void ScrollView::gatherTouchMove(const Vec2& delta)
{
while(_touchMoveDisplacements.size() >= NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED)
{
_touchMoveDisplacements.pop_front();
_touchMoveTimeDeltas.pop_front();
}
_touchMoveDisplacements.push_back(delta);
long long timestamp = utils::getTimeInMilliseconds();
_touchMoveTimeDeltas.push_back((timestamp - _touchMovePreviousTimestamp) / 1000.0f);
_touchMovePreviousTimestamp = timestamp;
}
void ScrollView::handlePressLogic(Touch *touch)
{
_bePressed = true;
_autoScrolling = false;
_inertiaScrolling = false;
// Initialize touch move information
// Clear gathered touch move information
{
_touchMovePreviousTimestamp = utils::getTimeInMilliseconds();
_touchMoveDisplacements.clear();
@ -858,9 +900,7 @@ void ScrollView::handlePressLogic(Touch *touch)
void ScrollView::handleMoveLogic(Touch *touch)
{
Vec3 currPt, prevPt;
if (nullptr == _hittedByCamera ||
false == hitTest(touch->getLocation(), _hittedByCamera, &currPt) ||
false == hitTest(touch->getPreviousLocation(), _hittedByCamera, &prevPt))
if(!calculateCurrAndPrevTouchPoints(touch, &currPt, &prevPt))
{
return;
}
@ -869,22 +909,22 @@ void ScrollView::handleMoveLogic(Touch *touch)
scrollChildren(delta.x, delta.y);
// Gather touch move information for speed calculation
{
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;
}
gatherTouchMove(delta);
}
void ScrollView::handleReleaseLogic(Touch *touch)
{
// Gather the last touch information when released
{
Vec3 currPt, prevPt;
if(calculateCurrAndPrevTouchPoints(touch, &currPt, &prevPt))
{
Vec3 delta3 = currPt - prevPt;
Vec2 delta(delta3.x, delta3.y);
gatherTouchMove(delta);
}
}
_bePressed = false;
bool bounceBackStarted = startBounceBackIfNeeded();
@ -1377,14 +1417,12 @@ void ScrollView::copySpecialProperties(Widget *widget)
_autoScrollAttenuate = scrollView->_autoScrollAttenuate;
_autoScrollStartPosition = scrollView->_autoScrollStartPosition;
_autoScrollTargetDelta = scrollView->_autoScrollTargetDelta;
_autoScrollDuration = scrollView->_autoScrollDuration;
_autoScrollTotalTime = scrollView->_autoScrollTotalTime;
_autoScrollAccumulatedTime = scrollView->_autoScrollAccumulatedTime;
_outOfBoundaryDuringInertiaScroll = scrollView->_outOfBoundaryDuringInertiaScroll;
_outOfBoundaryPositionDuringInertiaScroll = scrollView->_outOfBoundaryPositionDuringInertiaScroll;
_autoScrollCompleteCallback = scrollView->_autoScrollCompleteCallback;
_autoScrollMoveCallback = scrollView->_autoScrollMoveCallback;
_autoScrollCurrentlyOutOfBoundary = scrollView->_autoScrollCurrentlyOutOfBoundary;
_autoScrollBraking = scrollView->_autoScrollBraking;
_autoScrollBrakingStartPosition = scrollView->_autoScrollBrakingStartPosition;
setInertiaScrollEnabled(scrollView->_inertiaScrollEnabled);
_inertiaScrolling = scrollView->_inertiaScrolling;
setBounceEnabled(scrollView->_bounceEnabled);
_scrollViewEventListener = scrollView->_scrollViewEventListener;
_scrollViewEventSelector = scrollView->_scrollViewEventSelector;

View File

@ -156,83 +156,83 @@ public:
/**
* Scroll inner container to bottom boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToBottom(float second, bool attenuated);
void scrollToBottom(float timeInSec, bool attenuated);
/**
* Scroll inner container to top boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToTop(float second, bool attenuated);
void scrollToTop(float timeInSec, bool attenuated);
/**
* Scroll inner container to left boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToLeft(float second, bool attenuated);
void scrollToLeft(float timeInSec, bool attenuated);
/**
* Scroll inner container to right boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToRight(float time, bool attenuated);
void scrollToRight(float timeInSec, bool attenuated);
/**
* Scroll inner container to top and left boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToTopLeft(float second, bool attenuated);
void scrollToTopLeft(float timeInSec, bool attenuated);
/**
* Scroll inner container to top and right boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToTopRight(float time, bool attenuated);
void scrollToTopRight(float timeInSec, bool attenuated);
/**
* Scroll inner container to bottom and left boundary of scrollview.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToBottomLeft(float second, bool attenuated);
void scrollToBottomLeft(float timeInSec, bool attenuated);
/**
* Scroll inner container to bottom and right boundary of scrollview.
* @param second Time in seconds
* @param timeInSec Time in seconds
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToBottomRight(float time, bool attenuated);
void scrollToBottomRight(float timeInSec, bool attenuated);
/**
* Scroll inner container to vertical percent position of scrollview.
* @param percent A value between 0 and 100.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToPercentVertical(float percent, float second, bool attenuated);
void scrollToPercentVertical(float percent, float timeInSec, bool attenuated);
/**
* Scroll inner container to horizontal percent position of scrollview.
* @param percent A value between 0 and 100.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToPercentHorizontal(float percent, float second, bool attenuated);
void scrollToPercentHorizontal(float percent, float timeInSec, bool attenuated);
/**
* Scroll inner container to both direction percent position of scrollview.
* @param percent A value between 0 and 100.
* @param second Time in seconds.
* @param timeInSec Time in seconds.
* @param attenuated Whether scroll speed attenuate or not.
*/
void scrollToPercentBothDirection(const Vec2& percent, float second, bool attenuated);
void scrollToPercentBothDirection(const Vec2& percent, float timeInSec, bool attenuated);
/**
* Move inner container to bottom boundary of scrollview.
@ -565,22 +565,27 @@ protected:
virtual void initScrollBar();
virtual void removeScrollBar();
bool isOutOfBoundary(MoveDirection dir) const;
bool isOutOfBoundaryTopOrBottom() const;
bool isOutOfBoundaryLeftOrRight() const;
bool isOutOfBoundary() const;
Vec2 flattenVectorByDirection(const Vec2& vector);
virtual Vec2 getHowMuchOutOfBoundary(const Vec2& addition = Vec2::ZERO);
bool isOutOfBoundary(MoveDirection dir);
bool isOutOfBoundary();
void moveChildren(float offsetX, float offsetY);
void moveChildrenToPosition(const Vec2& position);
bool calculateCurrAndPrevTouchPoints(Touch* touch, Vec3* currPt, Vec3* prevPt);
void gatherTouchMove(const Vec2& delta);
Vec2 calculateTouchMoveVelocity() const;
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);
virtual void startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity);
void startAutoScroll(const Vec2& deltaMove, float timeInSec, bool attenuated);
void startAutoScrollToDestination(const Vec2& des, float timeInSec, bool attenuated);
bool isNecessaryAutoScrollBrake();
void processAutoScrolling(float deltaTime);
void startInertiaScroll(const Vec2& touchMoveVelocity);
bool startBounceBackIfNeeded();
void jumpToDestination(const Vec2& des);
@ -597,8 +602,6 @@ protected:
void processScrollingEvent();
void dispatchEvent(ScrollviewEventType scrollEventType, EventType eventType);
Vec2 getHowMuchOutOfBoundary(const Vec2& addition) const;
void updateScrollBar(const Vec2& outOfBoundary);
protected:
@ -624,19 +627,19 @@ protected:
bool _autoScrollAttenuate;
Vec2 _autoScrollStartPosition;
Vec2 _autoScrollTargetDelta;
float _autoScrollDuration;
float _autoScrollTotalTime;
float _autoScrollAccumulatedTime;
bool _outOfBoundaryDuringInertiaScroll;
Vec2 _outOfBoundaryPositionDuringInertiaScroll;
std::function<void()> _autoScrollCompleteCallback;
std::function<void(const Vec2& moveDelta)> _autoScrollMoveCallback;
bool _autoScrollCurrentlyOutOfBoundary;
bool _autoScrollBraking;
Vec2 _autoScrollBrakingStartPosition;
// Inertia scroll
bool _inertiaScrollEnabled;
bool _inertiaScrolling;
bool _bounceEnabled;
Vec2 _outOfBoundaryAmount;
bool _outOfBoundaryAmountDirty;
bool _scrollBarEnabled;
ScrollViewBar* _verticalScrollBar;
ScrollViewBar* _horizontalScrollBar;

View File

@ -7,8 +7,12 @@ const char* font_UIListViewTest = "fonts/Marker Felt.ttf";
UIListViewTests::UIListViewTests()
{
ADD_TEST_CASE(UIListViewTest_Vertical);
ADD_TEST_CASE(UIListViewTest_Horizontal);
ADD_TEST_CASE(UIListViewTest_Vertical);
ADD_TEST_CASE(UIListViewTest_ScrollToItemVertical);
ADD_TEST_CASE(UIListViewTest_ScrollToItemHorizontal);
ADD_TEST_CASE(UIListViewTest_MagneticVertical);
ADD_TEST_CASE(UIListViewTest_MagneticHorizontal);
ADD_TEST_CASE(Issue12692);
ADD_TEST_CASE(Issue8316);
}
@ -67,10 +71,7 @@ bool UIListViewTest_Vertical::init()
listView->setBackGroundImage("cocosui/green_edit.png");
listView->setBackGroundImageScale9Enabled(true);
listView->setContentSize(Size(240, 130));
listView->setPosition(Vec2((widgetSize.width - backgroundSize.width) / 2.0f +
(backgroundSize.width - listView->getContentSize().width) / 2.0f,
(widgetSize.height - backgroundSize.height) / 2.0f +
(backgroundSize.height - listView->getContentSize().height) / 2.0f));
listView->setPosition(Vec2((widgetSize - listView->getContentSize()) / 2.0f));
listView->addEventListener((ui::ListView::ccListViewCallback)CC_CALLBACK_2(UIListViewTest_Vertical::selectedItemEvent, this));
listView->addEventListener((ui::ListView::ccScrollViewCallback)CC_CALLBACK_2(UIListViewTest_Vertical::selectedItemEventScrollView,this));
listView->setScrollBarPositionFromCorner(Vec2(7, 7));
@ -84,8 +85,7 @@ bool UIListViewTest_Vertical::init()
Layout* default_item = Layout::create();
default_item->setTouchEnabled(true);
default_item->setContentSize(default_button->getContentSize());
default_button->setPosition(Vec2(default_item->getContentSize().width / 2.0f,
default_item->getContentSize().height / 2.0f));
default_button->setPosition(Vec2(default_item->getContentSize() / 2.0f));
default_item->addChild(default_button);
// set model
@ -165,6 +165,40 @@ bool UIListViewTest_Vertical::init()
// set items margin
listView->setItemsMargin(2.0f);
// Show the indexes of items on each boundary.
{
float position = 75;
// Labels
_indexLabels[0] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
_indexLabels[0]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_indexLabels[0]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, position));
_uiLayer->addChild(_indexLabels[0]);
_indexLabels[1] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
_indexLabels[1]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_indexLabels[1]->setPosition(_uiLayer->getContentSize() / 2 + Size(140, 0));
_uiLayer->addChild(_indexLabels[1]);
_indexLabels[2] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
_indexLabels[2]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_indexLabels[2]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, -position));
_uiLayer->addChild(_indexLabels[2]);
// Callback
listView->ScrollView::addEventListener([this](Ref* ref, ScrollView::EventType eventType) {
ListView* listView = dynamic_cast<ListView*>(ref);
if(listView == nullptr || eventType != ScrollView::EventType::CONTAINER_MOVED)
{
return;
}
auto bottom = listView->getBottommostItemInCurrentView();
auto center = listView->getCenterItemInCurrentView();
auto top = listView->getTopmostItemInCurrentView();
_indexLabels[0]->setString(StringUtils::format("Top index=%zd", listView->getIndex(top)));
_indexLabels[1]->setString(StringUtils::format("Center\nindex=%zd", listView->getIndex(center)));
_indexLabels[2]->setString(StringUtils::format("Bottom index=%zd", listView->getIndex(bottom)));
});
}
return true;
}
@ -545,3 +579,237 @@ bool Issue8316::init()
return false;
}
// UIListViewTest_ScrollToItem
bool UIListViewTest_ScrollToItem::init()
{
if(!UIScene::init())
{
return false;
}
Size layerSize = _uiLayer->getContentSize();
static int NUMBER_OF_ITEMS = 31;
_nextIndex = 0;
_titleLabel = Text::create("Scroll to item", "fonts/Marker Felt.ttf", 32);
_titleLabel->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_titleLabel->setPosition(Vec2(layerSize / 2) + Vec2(0, _titleLabel->getContentSize().height * 3.15f));
_uiLayer->addChild(_titleLabel, 3);
// Create the list view
_listView = ListView::create();
_listView->setDirection(getListViewDirection());
_listView->setBounceEnabled(true);
_listView->setBackGroundImage("cocosui/green_edit.png");
_listView->setBackGroundImageScale9Enabled(true);
_listView->setContentSize(layerSize / 2);
_listView->setScrollBarPositionFromCorner(Vec2(7, 7));
_listView->setItemsMargin(2.0f);
_listView->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_listView->setPosition(layerSize / 2);
_uiLayer->addChild(_listView);
// Guide line for center align
{
DrawNode* pNode = DrawNode::create();
Vec2 center = layerSize / 2;
if(getListViewDirection() == ScrollView::Direction::HORIZONTAL)
{
float halfY = 110;
pNode->drawLine(Vec2(center.x, center.y - halfY), Vec2(center.x, center.y + halfY), Color4F(0, 0, 0, 1));
}
else
{
float halfX = 150;
pNode->drawLine(Vec2(center.x - halfX, center.y), Vec2(center.x + halfX, center.y), Color4F(0, 0, 0, 1));
}
pNode->setLineWidth(2);
_uiLayer->addChild(pNode);
}
// Button
auto pButton = Button::create("cocosui/backtotoppressed.png", "cocosui/backtotopnormal.png");
pButton->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
pButton->setScale(0.8f);
pButton->setPosition(Vec2(layerSize / 2) + Vec2(120, -60));
pButton->setTitleText(StringUtils::format("Go to '%d'", _nextIndex));
pButton->addClickEventListener([this, pButton](Ref*) {
_listView->scrollToItem(_nextIndex, Vec2::ANCHOR_MIDDLE, Vec2::ANCHOR_MIDDLE);
_nextIndex = (_nextIndex + (NUMBER_OF_ITEMS / 2)) % NUMBER_OF_ITEMS;
pButton->setTitleText(StringUtils::format("Go to '%d'", _nextIndex));
});
_uiLayer->addChild(pButton);
// Add list items
static const Size BUTTON_SIZE(50, 40);
for (int i = 0; i < NUMBER_OF_ITEMS; ++i)
{
auto pButton = Button::create("cocosui/button.png", "cocosui/buttonHighlighted.png");
pButton->setContentSize(BUTTON_SIZE);
pButton->setScale9Enabled(true);
pButton->setTitleText(StringUtils::format("Button-%d", i));
_listView->pushBackCustomItem(pButton);
}
return true;
}
// UIListViewTest_Magnetic
bool UIListViewTest_Magnetic::init()
{
if(!UIScene::init())
{
return false;
}
Size layerSize = _uiLayer->getContentSize();
_titleLabel = Text::create("Magnetic scroll", "fonts/Marker Felt.ttf", 32);
_titleLabel->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_titleLabel->setPosition(Vec2(layerSize / 2) + Vec2(0, _titleLabel->getContentSize().height * 3.15f));
_uiLayer->addChild(_titleLabel, 3);
// Create the list view
_listView = ListView::create();
_listView->setDirection(getListViewDirection());
_listView->setBounceEnabled(true);
_listView->setBackGroundImage("cocosui/green_edit.png");
_listView->setBackGroundImageScale9Enabled(true);
_listView->setContentSize(layerSize / 2);
_listView->setScrollBarPositionFromCorner(Vec2(7, 7));
_listView->setItemsMargin(2.0f);
_listView->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_listView->setPosition(layerSize / 2);
_uiLayer->addChild(_listView);
// Guide line for center align
{
DrawNode* pNode = DrawNode::create();
Vec2 center = layerSize / 2;
if(getListViewDirection() == ScrollView::Direction::HORIZONTAL)
{
float halfY = 110;
pNode->drawLine(Vec2(center.x, center.y - halfY), Vec2(center.x, center.y + halfY), Color4F(0, 0, 0, 1));
}
else
{
float halfX = 150;
pNode->drawLine(Vec2(center.x - halfX, center.y), Vec2(center.x + halfX, center.y), Color4F(0, 0, 0, 1));
}
pNode->setLineWidth(2);
_uiLayer->addChild(pNode);
}
// Show the indexes of items on each boundary.
{
for(int i = 0; i < 5; ++i)
{
_indexLabels[i] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
_indexLabels[i]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
_uiLayer->addChild(_indexLabels[i]);
}
float deltaX = 145, deltaY = 90;
_indexLabels[0]->setPosition(_uiLayer->getContentSize() / 2 + Size(-deltaX, 0)); // left
_indexLabels[1]->setPosition(_uiLayer->getContentSize() / 2 + Size(deltaX, 0)); // right
_indexLabels[2]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, deltaY)); // top
_indexLabels[3]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, -deltaY)); // bottom
_indexLabels[4]->setPosition(_uiLayer->getContentSize() / 2 + Size(deltaX, deltaY)); // center
// Callback
_listView->ScrollView::addEventListener([this](Ref* ref, ScrollView::EventType eventType) {
ListView* listView = dynamic_cast<ListView*>(ref);
if(listView == nullptr || eventType != ScrollView::EventType::CONTAINER_MOVED)
{
return;
}
auto left = listView->getLeftmostItemInCurrentView();
auto right = listView->getRightmostItemInCurrentView();
auto top = listView->getTopmostItemInCurrentView();
auto bottom = listView->getBottommostItemInCurrentView();
auto center = listView->getCenterItemInCurrentView();
_indexLabels[0]->setString(StringUtils::format("Left\nindex=%zd", listView->getIndex(left)));
_indexLabels[1]->setString(StringUtils::format("RIght\nindex=%zd", listView->getIndex(right)));
_indexLabels[2]->setString(StringUtils::format("Top index=%zd", listView->getIndex(top)));
_indexLabels[3]->setString(StringUtils::format("Bottom index=%zd", listView->getIndex(bottom)));
_indexLabels[4]->setString(StringUtils::format("Center\nindex=%zd", listView->getIndex(center)));
});
}
// Initial magnetic type
_listView->setMagneticType(ListView::MagneticType::NONE);
_titleLabel->setString("MagneticType - NONE");
// Magnetic change button
auto pButton = Button::create("cocosui/backtotoppressed.png", "cocosui/backtotopnormal.png");
pButton->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
pButton->setScale(0.8f);
pButton->setPosition(Vec2(layerSize / 2) + Vec2(130, -60));
pButton->setTitleText("Next Magnetic");
pButton->addClickEventListener([this](Ref*) {
ListView::MagneticType eCurrentType = _listView->getMagneticType();
ListView::MagneticType eNextType;
std::string sString;
if(eCurrentType == ListView::MagneticType::NONE)
{
eNextType = ListView::MagneticType::CENTER;
sString = "CENTER";
}
else if(eCurrentType == ListView::MagneticType::CENTER)
{
eNextType = ListView::MagneticType::BOTH_END;
sString = "BOTH_END";
}
else if(eCurrentType == ListView::MagneticType::BOTH_END)
{
if(getListViewDirection() == ScrollView::Direction::HORIZONTAL)
{
eNextType = ListView::MagneticType::LEFT;
sString = "LEFT";
}
else
{
eNextType = ListView::MagneticType::TOP;
sString = "TOP";
}
}
else if(eCurrentType == ListView::MagneticType::LEFT)
{
eNextType = ListView::MagneticType::RIGHT;
sString = "RIGHT";
}
else if(eCurrentType == ListView::MagneticType::RIGHT)
{
eNextType = ListView::MagneticType::NONE;
sString = "NONE";
}
else if(eCurrentType == ListView::MagneticType::TOP)
{
eNextType = ListView::MagneticType::BOTTOM;
sString = "BOTTOM";
}
else if(eCurrentType == ListView::MagneticType::BOTTOM)
{
eNextType = ListView::MagneticType::NONE;
sString = "NONE";
}
_listView->setMagneticType(eNextType);
_titleLabel->setString(StringUtils::format("MagneticType - %s", sString.c_str()));
});
_uiLayer->addChild(pButton);
// Add list items
static const Size BUTTON_SIZE(100, 70);
for (int i = 0; i < 40; ++i)
{
auto pButton = Button::create("cocosui/button.png", "cocosui/buttonHighlighted.png");
pButton->setContentSize(BUTTON_SIZE);
pButton->setScale9Enabled(true);
pButton->setTitleText(StringUtils::format("Button-%d", i));
_listView->pushBackCustomItem(pButton);
}
return true;
}

View File

@ -47,6 +47,8 @@ protected:
cocos2d::ui::Text* _displayValueLabel;
std::vector<std::string> _array;
cocos2d::ui::Text* _indexLabels[3];
};
class UIListViewTest_Horizontal : public UIScene
@ -84,4 +86,71 @@ public:
virtual bool init() override;
};
// Test for scroll to item
class UIListViewTest_ScrollToItem : public UIScene
{
protected:
virtual bool init() override;
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const = 0;
cocos2d::ui::ListView* _listView;
cocos2d::ui::Text* _titleLabel;
int _nextIndex;
};
class UIListViewTest_ScrollToItemVertical : public UIListViewTest_ScrollToItem
{
public:
CREATE_FUNC(UIListViewTest_ScrollToItemVertical);
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const
{
return cocos2d::ui::ScrollView::Direction::VERTICAL;
}
};
class UIListViewTest_ScrollToItemHorizontal : public UIListViewTest_ScrollToItem
{
public:
CREATE_FUNC(UIListViewTest_ScrollToItemHorizontal);
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const
{
return cocos2d::ui::ScrollView::Direction::HORIZONTAL;
}
};
// Test for magnetic scroll
class UIListViewTest_Magnetic : public UIScene
{
protected:
virtual bool init() override;
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const = 0;
cocos2d::ui::ListView* _listView;
cocos2d::ui::Text* _titleLabel;
cocos2d::ui::Text* _indexLabels[5];
};
class UIListViewTest_MagneticVertical : public UIListViewTest_Magnetic
{
public:
CREATE_FUNC(UIListViewTest_MagneticVertical);
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const
{
return cocos2d::ui::ScrollView::Direction::VERTICAL;
}
};
class UIListViewTest_MagneticHorizontal : public UIListViewTest_Magnetic
{
public:
CREATE_FUNC(UIListViewTest_MagneticHorizontal);
virtual cocos2d::ui::ScrollView::Direction getListViewDirection() const
{
return cocos2d::ui::ScrollView::Direction::HORIZONTAL;
}
};
#endif /* defined(__TestCpp__UIListViewTest__) */