UIListView.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096
  1. /****************************************************************************
  2. Copyright (c) 2013-2016 Chukong Technologies Inc.
  3. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
  4. http://www.cocos2d-x.org
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in
  12. all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. ****************************************************************************/
  21. #include "ui/UIListView.h"
  22. #include "ui/UIHelper.h"
  23. NS_CC_BEGIN
  24. static const float DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM = 1.0f;
  25. namespace ui {
  26. IMPLEMENT_CLASS_GUI_INFO(ListView)
  27. ListView::ListView():
  28. _model(nullptr),
  29. _gravity(Gravity::CENTER_VERTICAL),
  30. _magneticType(MagneticType::NONE),
  31. _magneticAllowedOutOfBoundary(true),
  32. _itemsMargin(0.0f),
  33. _leftPadding(0.0f),
  34. _topPadding(0.0f),
  35. _rightPadding(0.0f),
  36. _bottomPadding(0.0f),
  37. _scrollTime(DEFAULT_TIME_IN_SEC_FOR_SCROLL_TO_ITEM),
  38. _curSelectedIndex(-1),
  39. _innerContainerDoLayoutDirty(true),
  40. _listViewEventListener(nullptr),
  41. _listViewEventSelector(nullptr),
  42. _eventCallback(nullptr)
  43. {
  44. this->setTouchEnabled(true);
  45. }
  46. ListView::~ListView()
  47. {
  48. _listViewEventListener = nullptr;
  49. _listViewEventSelector = nullptr;
  50. _items.clear();
  51. CC_SAFE_RELEASE(_model);
  52. }
  53. ListView* ListView::create()
  54. {
  55. ListView* widget = new (std::nothrow) ListView();
  56. if (widget && widget->init())
  57. {
  58. widget->autorelease();
  59. return widget;
  60. }
  61. CC_SAFE_DELETE(widget);
  62. return nullptr;
  63. }
  64. bool ListView::init()
  65. {
  66. if (ScrollView::init())
  67. {
  68. setDirection(Direction::VERTICAL);
  69. return true;
  70. }
  71. return false;
  72. }
  73. void ListView::setItemModel(Widget *model)
  74. {
  75. if (nullptr == model)
  76. {
  77. CCLOG("Can't set a nullptr to item model!");
  78. return;
  79. }
  80. CC_SAFE_RELEASE_NULL(_model);
  81. _model = model;
  82. CC_SAFE_RETAIN(_model);
  83. }
  84. void ListView::handleReleaseLogic(Touch *touch)
  85. {
  86. ScrollView::handleReleaseLogic(touch);
  87. if(!_autoScrolling)
  88. {
  89. startMagneticScroll();
  90. }
  91. }
  92. void ListView::onItemListChanged()
  93. {
  94. _outOfBoundaryAmountDirty = true;
  95. }
  96. void ListView::updateInnerContainerSize()
  97. {
  98. switch (_direction)
  99. {
  100. case Direction::VERTICAL:
  101. {
  102. size_t length = _items.size();
  103. float totalHeight = (length == 0) ? 0.0f : (length - 1) * _itemsMargin + (_topPadding + _bottomPadding);
  104. for (auto& item : _items)
  105. {
  106. totalHeight += item->getContentSize().height;
  107. }
  108. float finalWidth = _contentSize.width;
  109. float finalHeight = totalHeight;
  110. setInnerContainerSize(Size(finalWidth, finalHeight));
  111. break;
  112. }
  113. case Direction::HORIZONTAL:
  114. {
  115. size_t length = _items.size();
  116. float totalWidth = (length == 0) ? 0.0f : (length - 1) * _itemsMargin + (_leftPadding + _rightPadding);
  117. for (auto& item : _items)
  118. {
  119. totalWidth += item->getContentSize().width;
  120. }
  121. float finalWidth = totalWidth;
  122. float finalHeight = _contentSize.height;
  123. setInnerContainerSize(Size(finalWidth, finalHeight));
  124. break;
  125. }
  126. default:
  127. break;
  128. }
  129. }
  130. void ListView::remedyVerticalLayoutParameter(LinearLayoutParameter* layoutParameter, ssize_t itemIndex)
  131. {
  132. CCASSERT(nullptr != layoutParameter, "Layout parameter can't be nullptr!");
  133. switch (_gravity)
  134. {
  135. case Gravity::LEFT:
  136. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::LEFT);
  137. break;
  138. case Gravity::RIGHT:
  139. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::RIGHT);
  140. break;
  141. case Gravity::CENTER_HORIZONTAL:
  142. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::CENTER_HORIZONTAL);
  143. break;
  144. default:
  145. break;
  146. }
  147. if (0 == itemIndex)
  148. {
  149. layoutParameter->setMargin(Margin(_leftPadding, _topPadding, _rightPadding, 0.f));
  150. }
  151. else if (_items.size() - 1 == itemIndex)
  152. {
  153. layoutParameter->setMargin(Margin(_leftPadding, _itemsMargin, _rightPadding, _bottomPadding));
  154. }
  155. else
  156. {
  157. layoutParameter->setMargin(Margin(_leftPadding, _itemsMargin, _rightPadding, 0.0f));
  158. }
  159. }
  160. void ListView::remedyHorizontalLayoutParameter(LinearLayoutParameter* layoutParameter, ssize_t itemIndex)
  161. {
  162. CCASSERT(nullptr != layoutParameter, "Layout parameter can't be nullptr!");
  163. switch (_gravity)
  164. {
  165. case Gravity::TOP:
  166. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::TOP);
  167. break;
  168. case Gravity::BOTTOM:
  169. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::BOTTOM);
  170. break;
  171. case Gravity::CENTER_VERTICAL:
  172. layoutParameter->setGravity(LinearLayoutParameter::LinearGravity::CENTER_VERTICAL);
  173. break;
  174. default:
  175. break;
  176. }
  177. if (0 == itemIndex)
  178. {
  179. layoutParameter->setMargin(Margin(_leftPadding, _topPadding, 0.f, _bottomPadding));
  180. }
  181. else if (_items.size() == itemIndex)
  182. {
  183. layoutParameter->setMargin(Margin(_itemsMargin, _topPadding, _rightPadding, _bottomPadding));
  184. }
  185. else
  186. {
  187. layoutParameter->setMargin(Margin(_itemsMargin, _topPadding, 0.f, _bottomPadding));
  188. }
  189. }
  190. void ListView::remedyLayoutParameter(Widget *item)
  191. {
  192. CCASSERT(nullptr != item, "ListView Item can't be nullptr!");
  193. LinearLayoutParameter* linearLayoutParameter = (LinearLayoutParameter*)(item->getLayoutParameter());
  194. bool isLayoutParameterExists = true;
  195. if (!linearLayoutParameter)
  196. {
  197. linearLayoutParameter = LinearLayoutParameter::create();
  198. isLayoutParameterExists = false;
  199. }
  200. ssize_t itemIndex = getIndex(item);
  201. switch (_direction)
  202. {
  203. case Direction::VERTICAL:
  204. {
  205. this->remedyVerticalLayoutParameter(linearLayoutParameter, itemIndex);
  206. break;
  207. }
  208. case Direction::HORIZONTAL:
  209. {
  210. this->remedyHorizontalLayoutParameter(linearLayoutParameter, itemIndex);
  211. break;
  212. }
  213. default:
  214. break;
  215. }
  216. if (!isLayoutParameterExists)
  217. {
  218. item->setLayoutParameter(linearLayoutParameter);
  219. }
  220. }
  221. void ListView::pushBackDefaultItem()
  222. {
  223. if (nullptr == _model)
  224. {
  225. return;
  226. }
  227. Widget* newItem = _model->clone();
  228. remedyLayoutParameter(newItem);
  229. addChild(newItem);
  230. requestDoLayout();
  231. }
  232. void ListView::insertDefaultItem(ssize_t index)
  233. {
  234. if (nullptr == _model)
  235. {
  236. return;
  237. }
  238. insertCustomItem(_model->clone(), index);
  239. }
  240. void ListView::pushBackCustomItem(Widget* item)
  241. {
  242. remedyLayoutParameter(item);
  243. addChild(item);
  244. requestDoLayout();
  245. }
  246. void ListView::addChild(cocos2d::Node *child, int zOrder, int tag)
  247. {
  248. ScrollView::addChild(child, zOrder, tag);
  249. Widget* widget = dynamic_cast<Widget*>(child);
  250. if (nullptr != widget)
  251. {
  252. _items.pushBack(widget);
  253. onItemListChanged();
  254. }
  255. }
  256. void ListView::addChild(cocos2d::Node *child)
  257. {
  258. ListView::addChild(child, child->getLocalZOrder(), child->getName());
  259. }
  260. void ListView::addChild(cocos2d::Node *child, int zOrder)
  261. {
  262. ListView::addChild(child, zOrder, child->getName());
  263. }
  264. void ListView::addChild(Node* child, int zOrder, const std::string &name)
  265. {
  266. ScrollView::addChild(child, zOrder, name);
  267. Widget* widget = dynamic_cast<Widget*>(child);
  268. if (nullptr != widget)
  269. {
  270. _items.pushBack(widget);
  271. onItemListChanged();
  272. }
  273. }
  274. void ListView::removeChild(cocos2d::Node *child, bool cleanup)
  275. {
  276. Widget* widget = dynamic_cast<Widget*>(child);
  277. if (nullptr != widget)
  278. {
  279. if (-1 != _curSelectedIndex)
  280. {
  281. auto removedIndex = getIndex(widget);
  282. if (_curSelectedIndex > removedIndex)
  283. {
  284. _curSelectedIndex -= 1;
  285. }
  286. else if (_curSelectedIndex == removedIndex)
  287. {
  288. _curSelectedIndex = -1;
  289. }
  290. }
  291. _items.eraseObject(widget);
  292. onItemListChanged();
  293. }
  294. ScrollView::removeChild(child, cleanup);
  295. requestDoLayout();
  296. }
  297. void ListView::removeAllChildren()
  298. {
  299. this->removeAllChildrenWithCleanup(true);
  300. }
  301. void ListView::removeAllChildrenWithCleanup(bool cleanup)
  302. {
  303. ScrollView::removeAllChildrenWithCleanup(cleanup);
  304. _curSelectedIndex = -1;
  305. _items.clear();
  306. onItemListChanged();
  307. }
  308. void ListView::insertCustomItem(Widget* item, ssize_t index)
  309. {
  310. if (-1 != _curSelectedIndex)
  311. {
  312. if (_curSelectedIndex >= index)
  313. {
  314. _curSelectedIndex += 1;
  315. }
  316. }
  317. _items.insert(index, item);
  318. onItemListChanged();
  319. ScrollView::addChild(item);
  320. remedyLayoutParameter(item);
  321. requestDoLayout();
  322. }
  323. void ListView::removeItem(ssize_t index)
  324. {
  325. Widget* item = getItem(index);
  326. if (nullptr == item)
  327. {
  328. return;
  329. }
  330. removeChild(item, true);
  331. }
  332. void ListView::removeLastItem()
  333. {
  334. removeItem(_items.size() -1);
  335. }
  336. void ListView::removeAllItems()
  337. {
  338. removeAllChildren();
  339. }
  340. Widget* ListView::getItem(ssize_t index) const
  341. {
  342. if (index < 0 || index >= _items.size())
  343. {
  344. return nullptr;
  345. }
  346. return _items.at(index);
  347. }
  348. Vector<Widget*>& ListView::getItems()
  349. {
  350. return _items;
  351. }
  352. ssize_t ListView::getIndex(Widget *item) const
  353. {
  354. if (nullptr == item)
  355. {
  356. return -1;
  357. }
  358. return _items.getIndex(item);
  359. }
  360. void ListView::setGravity(Gravity gravity)
  361. {
  362. if (_gravity == gravity)
  363. {
  364. return;
  365. }
  366. _gravity = gravity;
  367. requestDoLayout();
  368. }
  369. void ListView::setMagneticType(MagneticType magneticType)
  370. {
  371. _magneticType = magneticType;
  372. _outOfBoundaryAmountDirty = true;
  373. startMagneticScroll();
  374. }
  375. ListView::MagneticType ListView::getMagneticType() const
  376. {
  377. return _magneticType;
  378. }
  379. void ListView::setMagneticAllowedOutOfBoundary(bool magneticAllowedOutOfBoundary)
  380. {
  381. _magneticAllowedOutOfBoundary = magneticAllowedOutOfBoundary;
  382. }
  383. bool ListView::getMagneticAllowedOutOfBoundary() const
  384. {
  385. return _magneticAllowedOutOfBoundary;
  386. }
  387. void ListView::setItemsMargin(float margin)
  388. {
  389. if (_itemsMargin == margin)
  390. {
  391. return;
  392. }
  393. _itemsMargin = margin;
  394. requestDoLayout();
  395. }
  396. float ListView::getItemsMargin()const
  397. {
  398. return _itemsMargin;
  399. }
  400. void ListView::setPadding(float l, float t, float r, float b)
  401. {
  402. if (l == _leftPadding && t == _topPadding && r == _rightPadding && b == _bottomPadding)
  403. {
  404. return;
  405. }
  406. _leftPadding = l;
  407. _topPadding = t;
  408. _rightPadding = r;
  409. _bottomPadding = b;
  410. requestDoLayout();
  411. }
  412. void ListView::setLeftPadding(float l)
  413. {
  414. if (l == _leftPadding)
  415. {
  416. return;
  417. }
  418. _leftPadding = l;
  419. requestDoLayout();
  420. }
  421. void ListView::setTopPadding(float t)
  422. {
  423. if (t == _topPadding)
  424. {
  425. return;
  426. }
  427. _topPadding = t;
  428. requestDoLayout();
  429. }
  430. void ListView::setRightPadding(float r)
  431. {
  432. if (r == _rightPadding)
  433. {
  434. return;
  435. }
  436. _rightPadding = r;
  437. requestDoLayout();
  438. }
  439. void ListView::setBottomPadding(float b)
  440. {
  441. if (b == _bottomPadding)
  442. {
  443. return;
  444. }
  445. _bottomPadding = b;
  446. requestDoLayout();
  447. }
  448. float ListView::getLeftPadding() const
  449. {
  450. return _leftPadding;
  451. }
  452. float ListView::getTopPadding() const
  453. {
  454. return _topPadding;
  455. }
  456. float ListView::getRightPadding() const
  457. {
  458. return _rightPadding;
  459. }
  460. float ListView::getBottomPadding() const
  461. {
  462. return _bottomPadding;
  463. }
  464. void ListView::setScrollDuration(float time)
  465. {
  466. if (time >= 0)
  467. _scrollTime = time;
  468. }
  469. float ListView::getScrollDuration() const
  470. {
  471. return _scrollTime;
  472. }
  473. void ListView::setDirection(Direction dir)
  474. {
  475. switch (dir)
  476. {
  477. case Direction::NONE:
  478. case Direction::BOTH:
  479. break;
  480. case Direction::VERTICAL:
  481. setLayoutType(Type::VERTICAL);
  482. break;
  483. case Direction::HORIZONTAL:
  484. setLayoutType(Type::HORIZONTAL);
  485. break;
  486. default:
  487. return;
  488. break;
  489. }
  490. ScrollView::setDirection(dir);
  491. }
  492. void ListView::refreshView()
  493. {
  494. forceDoLayout();
  495. }
  496. void ListView::requestDoLayout()
  497. {
  498. _innerContainerDoLayoutDirty = true;
  499. }
  500. void ListView::doLayout()
  501. {
  502. if(!_innerContainerDoLayoutDirty)
  503. {
  504. return;
  505. }
  506. ssize_t length = _items.size();
  507. for (int i = 0; i < length; ++i)
  508. {
  509. Widget* item = _items.at(i);
  510. item->setLocalZOrder(i);
  511. remedyLayoutParameter(item);
  512. }
  513. updateInnerContainerSize();
  514. _innerContainer->forceDoLayout();
  515. _innerContainerDoLayoutDirty = false;
  516. }
  517. void ListView::addEventListenerListView(Ref *target, SEL_ListViewEvent selector)
  518. {
  519. _listViewEventListener = target;
  520. _listViewEventSelector = selector;
  521. }
  522. void ListView::addEventListener(const ccListViewCallback& callback)
  523. {
  524. _eventCallback = callback;
  525. }
  526. void ListView::selectedItemEvent(TouchEventType event)
  527. {
  528. this->retain();
  529. switch (event)
  530. {
  531. case TouchEventType::BEGAN:
  532. {
  533. if (_listViewEventListener && _listViewEventSelector)
  534. {
  535. (_listViewEventListener->*_listViewEventSelector)(this, LISTVIEW_ONSELECTEDITEM_START);
  536. }
  537. if (_eventCallback) {
  538. _eventCallback(this,EventType::ON_SELECTED_ITEM_START);
  539. }
  540. if (_ccEventCallback)
  541. {
  542. _ccEventCallback(this, static_cast<int>(EventType::ON_SELECTED_ITEM_START));
  543. }
  544. }
  545. break;
  546. default:
  547. {
  548. if (_listViewEventListener && _listViewEventSelector)
  549. {
  550. (_listViewEventListener->*_listViewEventSelector)(this, LISTVIEW_ONSELECTEDITEM_END);
  551. }
  552. if (_eventCallback) {
  553. _eventCallback(this, EventType::ON_SELECTED_ITEM_END);
  554. }
  555. if (_ccEventCallback)
  556. {
  557. _ccEventCallback(this, static_cast<int>(EventType::ON_SELECTED_ITEM_END));
  558. }
  559. }
  560. break;
  561. }
  562. this->release();
  563. }
  564. void ListView::interceptTouchEvent(TouchEventType event, Widget *sender, Touch* touch)
  565. {
  566. ScrollView::interceptTouchEvent(event, sender, touch);
  567. if (!_touchEnabled)
  568. {
  569. return;
  570. }
  571. if (event != TouchEventType::MOVED)
  572. {
  573. Widget* parent = sender;
  574. while (parent)
  575. {
  576. if (parent && (parent->getParent() == _innerContainer))
  577. {
  578. _curSelectedIndex = getIndex(parent);
  579. break;
  580. }
  581. parent = dynamic_cast<Widget*>(parent->getParent());
  582. }
  583. if (sender->isHighlighted()) {
  584. selectedItemEvent(event);
  585. }
  586. }
  587. }
  588. static Vec2 calculateItemPositionWithAnchor(Widget* item, const Vec2& itemAnchorPoint)
  589. {
  590. Vec2 origin(item->getLeftBoundary(), item->getBottomBoundary());
  591. Size size = item->getContentSize();
  592. return origin + Vec2(size.width * itemAnchorPoint.x, size.height * itemAnchorPoint.y);
  593. }
  594. static Widget* findClosestItem(const Vec2& targetPosition, const Vector<Widget*>& items, const Vec2& itemAnchorPoint, ssize_t firstIndex, float distanceFromFirst, ssize_t lastIndex, float distanceFromLast)
  595. {
  596. CCASSERT(firstIndex >= 0 && lastIndex < items.size() && firstIndex <= lastIndex, "");
  597. if (firstIndex == lastIndex)
  598. {
  599. return items.at(firstIndex);
  600. }
  601. if (lastIndex - firstIndex == 1)
  602. {
  603. if (distanceFromFirst <= distanceFromLast)
  604. {
  605. return items.at(firstIndex);
  606. }
  607. else
  608. {
  609. return items.at(lastIndex);
  610. }
  611. }
  612. // Binary search
  613. ssize_t midIndex = (firstIndex + lastIndex) / 2;
  614. Vec2 itemPosition = calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint);
  615. float distanceFromMid = (targetPosition - itemPosition).length();
  616. if (distanceFromFirst <= distanceFromLast)
  617. {
  618. // Left half
  619. return findClosestItem(targetPosition, items, itemAnchorPoint, firstIndex, distanceFromFirst, midIndex, distanceFromMid);
  620. }
  621. else
  622. {
  623. // Right half
  624. return findClosestItem(targetPosition, items, itemAnchorPoint, midIndex, distanceFromMid, lastIndex, distanceFromLast);
  625. }
  626. }
  627. Widget* ListView::getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint) const
  628. {
  629. if (_items.empty())
  630. {
  631. return nullptr;
  632. }
  633. // Find the closest item through binary search
  634. ssize_t firstIndex = 0;
  635. Vec2 firstPosition = calculateItemPositionWithAnchor(_items.at(firstIndex), itemAnchorPoint);
  636. float distanceFromFirst = (targetPosition - firstPosition).length();
  637. ssize_t lastIndex = _items.size() - 1;
  638. Vec2 lastPosition = calculateItemPositionWithAnchor(_items.at(lastIndex), itemAnchorPoint);
  639. float distanceFromLast = (targetPosition - lastPosition).length();
  640. return findClosestItem(targetPosition, _items, itemAnchorPoint, firstIndex, distanceFromFirst, lastIndex, distanceFromLast);
  641. }
  642. Widget* ListView::getClosestItemToPositionInCurrentView(const Vec2& positionRatioInView, const Vec2& itemAnchorPoint) const
  643. {
  644. // Calculate the target position
  645. Size contentSize = getContentSize();
  646. Vec2 targetPosition = -_innerContainer->getPosition();
  647. targetPosition.x += contentSize.width * positionRatioInView.x;
  648. targetPosition.y += contentSize.height * positionRatioInView.y;
  649. return getClosestItemToPosition(targetPosition, itemAnchorPoint);
  650. }
  651. Widget* ListView::getCenterItemInCurrentView() const
  652. {
  653. return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE, Vec2::ANCHOR_MIDDLE);
  654. }
  655. Widget* ListView::getLeftmostItemInCurrentView() const
  656. {
  657. if (_direction == Direction::HORIZONTAL)
  658. {
  659. return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_LEFT, Vec2::ANCHOR_MIDDLE);
  660. }
  661. return nullptr;
  662. }
  663. Widget* ListView::getRightmostItemInCurrentView() const
  664. {
  665. if (_direction == Direction::HORIZONTAL)
  666. {
  667. return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_RIGHT, Vec2::ANCHOR_MIDDLE);
  668. }
  669. return nullptr;
  670. }
  671. Widget* ListView::getTopmostItemInCurrentView() const
  672. {
  673. if (_direction == Direction::VERTICAL)
  674. {
  675. return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_TOP, Vec2::ANCHOR_MIDDLE);
  676. }
  677. return nullptr;
  678. }
  679. Widget* ListView::getBottommostItemInCurrentView() const
  680. {
  681. if (_direction == Direction::VERTICAL)
  682. {
  683. return getClosestItemToPositionInCurrentView(Vec2::ANCHOR_MIDDLE_BOTTOM, Vec2::ANCHOR_MIDDLE);
  684. }
  685. return nullptr;
  686. }
  687. void ListView::jumpToBottom()
  688. {
  689. doLayout();
  690. ScrollView::jumpToBottom();
  691. }
  692. void ListView::jumpToTop()
  693. {
  694. doLayout();
  695. ScrollView::jumpToTop();
  696. }
  697. void ListView::jumpToLeft()
  698. {
  699. doLayout();
  700. ScrollView::jumpToLeft();
  701. }
  702. void ListView::jumpToRight()
  703. {
  704. doLayout();
  705. ScrollView::jumpToRight();
  706. }
  707. void ListView::jumpToTopLeft()
  708. {
  709. doLayout();
  710. ScrollView::jumpToTopLeft();
  711. }
  712. void ListView::jumpToTopRight()
  713. {
  714. doLayout();
  715. ScrollView::jumpToTopRight();
  716. }
  717. void ListView::jumpToBottomLeft()
  718. {
  719. doLayout();
  720. ScrollView::jumpToBottomLeft();
  721. }
  722. void ListView::jumpToBottomRight()
  723. {
  724. doLayout();
  725. ScrollView::jumpToBottomRight();
  726. }
  727. void ListView::jumpToPercentVertical(float percent)
  728. {
  729. doLayout();
  730. ScrollView::jumpToPercentVertical(percent);
  731. }
  732. void ListView::jumpToPercentHorizontal(float percent)
  733. {
  734. doLayout();
  735. ScrollView::jumpToPercentHorizontal(percent);
  736. }
  737. void ListView::jumpToPercentBothDirection(const Vec2& percent)
  738. {
  739. doLayout();
  740. ScrollView::jumpToPercentBothDirection(percent);
  741. }
  742. Vec2 ListView::calculateItemDestination(const Vec2& positionRatioInView, Widget* item, const Vec2& itemAnchorPoint)
  743. {
  744. const Size& contentSize = getContentSize();
  745. Vec2 positionInView;
  746. positionInView.x += contentSize.width * positionRatioInView.x;
  747. positionInView.y += contentSize.height * positionRatioInView.y;
  748. Vec2 itemPosition = calculateItemPositionWithAnchor(item, itemAnchorPoint);
  749. return -(itemPosition - positionInView);
  750. }
  751. void ListView::jumpToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint)
  752. {
  753. Widget* item = getItem(itemIndex);
  754. if (item == nullptr)
  755. {
  756. return;
  757. }
  758. doLayout();
  759. Vec2 destination = calculateItemDestination(positionRatioInView, item, itemAnchorPoint);
  760. if(!_bounceEnabled)
  761. {
  762. Vec2 delta = destination - getInnerContainerPosition();
  763. Vec2 outOfBoundary = getHowMuchOutOfBoundary(delta);
  764. destination += outOfBoundary;
  765. }
  766. jumpToDestination(destination);
  767. }
  768. void ListView::scrollToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint)
  769. {
  770. scrollToItem(itemIndex, positionRatioInView, itemAnchorPoint, _scrollTime);
  771. }
  772. void ListView::scrollToItem(ssize_t itemIndex, const Vec2& positionRatioInView, const Vec2& itemAnchorPoint, float timeInSec)
  773. {
  774. Widget* item = getItem(itemIndex);
  775. if (item == nullptr)
  776. {
  777. return;
  778. }
  779. Vec2 destination = calculateItemDestination(positionRatioInView, item, itemAnchorPoint);
  780. startAutoScrollToDestination(destination, timeInSec, true);
  781. }
  782. ssize_t ListView::getCurSelectedIndex() const
  783. {
  784. return _curSelectedIndex;
  785. }
  786. void ListView::setCurSelectedIndex(int itemIndex)
  787. {
  788. Widget* item = getItem(itemIndex);
  789. if (item == nullptr)
  790. {
  791. return;
  792. }
  793. _curSelectedIndex = itemIndex;
  794. this->selectedItemEvent(cocos2d::ui::Widget::TouchEventType::ENDED);
  795. }
  796. void ListView::onSizeChanged()
  797. {
  798. ScrollView::onSizeChanged();
  799. requestDoLayout();
  800. }
  801. std::string ListView::getDescription() const
  802. {
  803. return "ListView";
  804. }
  805. Widget* ListView::createCloneInstance()
  806. {
  807. return ListView::create();
  808. }
  809. void ListView::copyClonedWidgetChildren(Widget* model)
  810. {
  811. auto& arrayItems = static_cast<ListView*>(model)->getItems();
  812. for (auto& item : arrayItems)
  813. {
  814. pushBackCustomItem(item->clone());
  815. }
  816. }
  817. void ListView::copySpecialProperties(Widget *widget)
  818. {
  819. ListView* listViewEx = dynamic_cast<ListView*>(widget);
  820. if (listViewEx)
  821. {
  822. ScrollView::copySpecialProperties(widget);
  823. setItemModel(listViewEx->_model);
  824. setItemsMargin(listViewEx->_itemsMargin);
  825. setGravity(listViewEx->_gravity);
  826. _listViewEventListener = listViewEx->_listViewEventListener;
  827. _listViewEventSelector = listViewEx->_listViewEventSelector;
  828. _eventCallback = listViewEx->_eventCallback;
  829. }
  830. }
  831. Vec2 ListView::getHowMuchOutOfBoundary(const Vec2& addition)
  832. {
  833. if(!_magneticAllowedOutOfBoundary || _items.empty())
  834. {
  835. return ScrollView::getHowMuchOutOfBoundary(addition);
  836. }
  837. else if(_magneticType == MagneticType::NONE || _magneticType == MagneticType::BOTH_END)
  838. {
  839. return ScrollView::getHowMuchOutOfBoundary(addition);
  840. }
  841. else if(addition == Vec2::ZERO && !_outOfBoundaryAmountDirty)
  842. {
  843. return _outOfBoundaryAmount;
  844. }
  845. // If it is allowed to be out of boundary by magnetic, adjust the boundaries according to the magnetic type.
  846. float leftBoundary = _leftBoundary;
  847. float rightBoundary = _rightBoundary;
  848. float topBoundary = _topBoundary;
  849. float bottomBoundary = _bottomBoundary;
  850. {
  851. ssize_t lastItemIndex = _items.size() - 1;
  852. Size contentSize = getContentSize();
  853. Vec2 firstItemAdjustment, lastItemAdjustment;
  854. if(_magneticType == MagneticType::CENTER)
  855. {
  856. firstItemAdjustment = (contentSize - _items.at(0)->getContentSize()) / 2;
  857. lastItemAdjustment = (contentSize - _items.at(lastItemIndex)->getContentSize()) / 2;
  858. }
  859. else if(_magneticType == MagneticType::LEFT)
  860. {
  861. lastItemAdjustment = contentSize - _items.at(lastItemIndex)->getContentSize();
  862. }
  863. else if(_magneticType == MagneticType::RIGHT)
  864. {
  865. firstItemAdjustment = contentSize - _items.at(0)->getContentSize();
  866. }
  867. else if(_magneticType == MagneticType::TOP)
  868. {
  869. lastItemAdjustment = contentSize - _items.at(lastItemIndex)->getContentSize();
  870. }
  871. else if(_magneticType == MagneticType::BOTTOM)
  872. {
  873. firstItemAdjustment = contentSize - _items.at(0)->getContentSize();
  874. }
  875. leftBoundary += firstItemAdjustment.x;
  876. rightBoundary -= lastItemAdjustment.x;
  877. topBoundary -= firstItemAdjustment.y;
  878. bottomBoundary += lastItemAdjustment.y;
  879. }
  880. // Calculate the actual amount
  881. Vec2 outOfBoundaryAmount;
  882. if(_innerContainer->getLeftBoundary() + addition.x > leftBoundary)
  883. {
  884. outOfBoundaryAmount.x = leftBoundary - (_innerContainer->getLeftBoundary() + addition.x);
  885. }
  886. else if(_innerContainer->getRightBoundary() + addition.x < rightBoundary)
  887. {
  888. outOfBoundaryAmount.x = rightBoundary - (_innerContainer->getRightBoundary() + addition.x);
  889. }
  890. if(_innerContainer->getTopBoundary() + addition.y < topBoundary)
  891. {
  892. outOfBoundaryAmount.y = topBoundary - (_innerContainer->getTopBoundary() + addition.y);
  893. }
  894. else if(_innerContainer->getBottomBoundary() + addition.y > bottomBoundary)
  895. {
  896. outOfBoundaryAmount.y = bottomBoundary - (_innerContainer->getBottomBoundary() + addition.y);
  897. }
  898. if(addition == Vec2::ZERO)
  899. {
  900. _outOfBoundaryAmount = outOfBoundaryAmount;
  901. _outOfBoundaryAmountDirty = false;
  902. }
  903. return outOfBoundaryAmount;
  904. }
  905. static Vec2 getAnchorPointByMagneticType(ListView::MagneticType magneticType)
  906. {
  907. switch(magneticType)
  908. {
  909. case ListView::MagneticType::NONE: return Vec2::ZERO;
  910. case ListView::MagneticType::BOTH_END: return Vec2::ANCHOR_TOP_LEFT;
  911. case ListView::MagneticType::CENTER: return Vec2::ANCHOR_MIDDLE;
  912. case ListView::MagneticType::LEFT: return Vec2::ANCHOR_MIDDLE_LEFT;
  913. case ListView::MagneticType::RIGHT: return Vec2::ANCHOR_MIDDLE_RIGHT;
  914. case ListView::MagneticType::TOP: return Vec2::ANCHOR_MIDDLE_TOP;
  915. case ListView::MagneticType::BOTTOM: return Vec2::ANCHOR_MIDDLE_BOTTOM;
  916. }
  917. return Vec2::ZERO;
  918. }
  919. void ListView::startAttenuatingAutoScroll(const Vec2& deltaMove, const Vec2& initialVelocity)
  920. {
  921. Vec2 adjustedDeltaMove = deltaMove;
  922. if(!_items.empty() && _magneticType != MagneticType::NONE)
  923. {
  924. adjustedDeltaMove = flattenVectorByDirection(adjustedDeltaMove);
  925. // If the destination is out of boundary, do nothing here. Because it will be handled by bouncing back.
  926. if(getHowMuchOutOfBoundary(adjustedDeltaMove) == Vec2::ZERO)
  927. {
  928. MagneticType magType = _magneticType;
  929. if(magType == MagneticType::BOTH_END)
  930. {
  931. if(_direction == Direction::HORIZONTAL)
  932. {
  933. magType = (adjustedDeltaMove.x > 0 ? MagneticType::LEFT : MagneticType::RIGHT);
  934. }
  935. else if(_direction == Direction::VERTICAL)
  936. {
  937. magType = (adjustedDeltaMove.y > 0 ? MagneticType::BOTTOM : MagneticType::TOP);
  938. }
  939. }
  940. // Adjust the delta move amount according to the magnetic type
  941. Vec2 magneticAnchorPoint = getAnchorPointByMagneticType(magType);
  942. Vec2 magneticPosition = -_innerContainer->getPosition();
  943. magneticPosition.x += getContentSize().width * magneticAnchorPoint.x;
  944. magneticPosition.y += getContentSize().height * magneticAnchorPoint.y;
  945. Widget* pTargetItem = getClosestItemToPosition(magneticPosition - adjustedDeltaMove, magneticAnchorPoint);
  946. Vec2 itemPosition = calculateItemPositionWithAnchor(pTargetItem, magneticAnchorPoint);
  947. adjustedDeltaMove = magneticPosition - itemPosition;
  948. }
  949. }
  950. ScrollView::startAttenuatingAutoScroll(adjustedDeltaMove, initialVelocity);
  951. }
  952. void ListView::startMagneticScroll()
  953. {
  954. if(_items.empty() || _magneticType == MagneticType::NONE)
  955. {
  956. return;
  957. }
  958. // Find the closest item
  959. Vec2 magneticAnchorPoint = getAnchorPointByMagneticType(_magneticType);
  960. Vec2 magneticPosition = -_innerContainer->getPosition();
  961. magneticPosition.x += getContentSize().width * magneticAnchorPoint.x;
  962. magneticPosition.y += getContentSize().height * magneticAnchorPoint.y;
  963. Widget* pTargetItem = getClosestItemToPosition(magneticPosition, magneticAnchorPoint);
  964. scrollToItem(getIndex(pTargetItem), magneticAnchorPoint, magneticAnchorPoint);
  965. }
  966. }
  967. NS_CC_END