ListView - Add APIs which return the closest item in specific position in current view.

This commit is contained in:
Neo Kim 2015-07-05 19:25:00 +09:00
parent a4e5f7c0a8
commit 2599271410
3 changed files with 197 additions and 7 deletions

View File

@ -529,6 +529,118 @@ 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);
}
Widget* ListView::getLeftmostItemInCurrentView() const
{
if (_direction == Direction::HORIZONTAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_LEFT);
}
return nullptr;
}
Widget* ListView::getRightmostItemInCurrentView() const
{
if (_direction == Direction::HORIZONTAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_RIGHT);
}
return nullptr;
}
Widget* ListView::getTopmostItemInCurrentView() const
{
if (_direction == Direction::VERTICAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_TOP);
}
return nullptr;
}
Widget* ListView::getBottommostItemInCurrentView() const
{
if (_direction == Direction::VERTICAL)
{
return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_BOTTOM);
}
return nullptr;
}
ssize_t ListView::getCurSelectedIndex() const
{
return _curSelectedIndex;

View File

@ -220,7 +220,54 @@ 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 = Vec2::ANCHOR_MIDDLE) const;
/**
* @brief Query the closest item to a specific position in current view.
*
* @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 = Vec2::ANCHOR_MIDDLE) 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 Query current selected widget's idnex.
*

View File

@ -67,10 +67,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 +81,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
@ -166,6 +162,41 @@ bool UIListViewTest_Vertical::init()
// set items margin
listView->setItemsMargin(2.0f);
// Show the indexes of items on each boundary.
{
float position = 75;
// Labels
Text* labels[3];
labels[0] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
labels[0]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
labels[0]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, position));
_uiLayer->addChild(labels[0]);
labels[1] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
labels[1]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
labels[1]->setPosition(_uiLayer->getContentSize() / 2 + Size(140, 0));
_uiLayer->addChild(labels[1]);
labels[2] = Text::create(" ", "fonts/Marker Felt.ttf", 12);
labels[2]->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
labels[2]->setPosition(_uiLayer->getContentSize() / 2 + Size(0, -position));
_uiLayer->addChild(labels[2]);
// Callback
listView->ScrollView::addEventListener([labels](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();
labels[0]->setString(StringUtils::format("Top index=%zd", listView->getIndex(top)));
labels[1]->setString(StringUtils::format("Center\nindex=%zd", listView->getIndex(center)));
labels[2]->setString(StringUtils::format("Bottom index=%zd", listView->getIndex(bottom)));
});
}
return true;
}