You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

502 lines
18 KiB

  1. -- 智能滚动界面
  2. --[[
  3. initCount ? : number, // 初始加载initCount个UIItem
  4. loadCount ? : number, // 滚动到增长边界时则加载loadCount个item
  5. initIndex ? : number, // 初始化索引位置,从1开始
  6. isPlayAnime ? : boolean, // 是否在第一次更新数据时播放动画
  7. isWheelStyle ? : boolean, // 是否滚轮风格
  8. focusPercent ? : number, // 从0到1,表示从下到上,从左到右,聚焦的百分比位置
  9. radiusRate ? : number, // 滚轮效果显著程度参数,越大越不明显
  10. coverRate ? : number, // ui条目覆盖效果,越大覆盖越明显
  11. autoScrollToFocus ? : boolean, // 自动将最近的ui回滚到焦点
  12. scrollDisScale ? : number, //
  13. --]]
  14. local SmartScrollView = class("SmartScrollView");
  15. function SmartScrollView:ctor(scrollView, param)
  16. self.initCount = (param and param.initCount) or 8;
  17. self.loadCount = (param and param.loadCount) or 3;
  18. self.initIndex = (param and param.initIndex) or 1;
  19. self.isPlayAnime = param and param.isPlayAnime;
  20. self.isWheelStyle = param and param.isWheelStyle;
  21. self.focusPercent = (param and param.focusPercent) or 0.5;
  22. self.radiusRate = (param and param.radiusRate) or app.config.Rule.SCROLL_VIEW_WHEEL_RADIUS_RATE.value;
  23. self.coverRate = (param and param.coverRate) or app.config.Rule.SCROLL_VIEW_WHEEL_COVER_RATE.value;
  24. self.autoScrollToFocus = param and param.autoScrollToFocus;
  25. self.scrollDisScale = (param and param.scrollDisScale) or 1;
  26. self.FocusRadius = 5; -- 焦点范围误差距离
  27. self.orderArray = {}; -- 数据必须连续有效,且每个数据皆可创建有效ui
  28. self.scrollItems = {}; -- Map<number , {layoutNode : cc.Widget , showObj : any}>(); 所有UIItem Map
  29. self.headDataIdx = 0;
  30. self.tailDataIdx = 1;
  31. self.dataChangeOnce = true;
  32. self.playAnimeOnce = true;
  33. self.scrollView = scrollView
  34. self:init();
  35. end
  36. -- 数据数组
  37. function SmartScrollView:getOrderArray()
  38. return self.orderArray;
  39. end
  40. -- 赋值触发初始化
  41. function SmartScrollView:setOrderArray(arr)
  42. -- 初始化数据获取方法,若数据变化,则UI重新加载
  43. local me = self
  44. me.orderArray = arr;
  45. if me.dataChangeOnce then
  46. me.dataChangeOnce = false;
  47. if not me.scrollView or tolua.isnull(me.scrollView) then
  48. print("SmartScrollView wasn't assigned UIScrollView!");
  49. return
  50. end
  51. me.scrollView:runDelay(0, function()
  52. me.dataChangeOnce = true;
  53. me:onDataChanged();
  54. end);
  55. end
  56. end
  57. function SmartScrollView:init()
  58. local me = self;
  59. local innerContainer = me.scrollView:getInnerContainer();
  60. innerContainer:setAutoSize(true);
  61. -- me.scrollView:setScrollDisScale(me.scrollDisScale);
  62. -- 隐藏进度条
  63. me.scrollView:getHBar():setVisible(false);
  64. me.scrollView:getVBar():setVisible(false);
  65. -- 滚轮风格初始化
  66. if me.isWheelStyle then
  67. local oldDoLayout = innerContainer.doLayout;
  68. innerContainer.doLayout = function()
  69. for k,v in ipairs(me.scrollItems) do
  70. v.layoutNode.LocalZOrder = k;
  71. end
  72. oldDoLayout(innerContainer);
  73. me.onScrollWheel();
  74. end
  75. -- 解决分辨率变化等导致的界面混乱问题
  76. me.scrollView:setWidgetEventListener(function(node, eventType)
  77. if eventType == cc.WidgetEvent.SIZE_CHANGED then
  78. me.scrollView.runDelay(0 , function()
  79. me.doLayout();
  80. end);
  81. end
  82. end);
  83. end
  84. -- 监听滚到顶部和底部(LocalZOrder大的,布局时排在下面)
  85. me.scrollView:addEventListener(function(target , eventType)
  86. if eventType == cc.ScrollviewEventType.scrollToTop or eventType == cc.ScrollviewEventType.scrollToLeft then
  87. if me.headDataIdx < 1 then
  88. return;
  89. end
  90. for i = 1 , me.loadCount do
  91. me:addItem(true);
  92. end
  93. if me.headDataIdx < 1 and me.isWheelStyle and #me.scrollItems > 0 then
  94. me:fillStart();
  95. end
  96. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  97. if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(1 , 0)) then
  98. innerContainer:setPositionX(innerContainer:getPositionX() + (1 - innerContainer:getAnchorPoint().x) * innerContainer:getSize().width);
  99. innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height);
  100. innerContainer:setAnchorPoint(cc.p(1 , 0));
  101. end
  102. else
  103. if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 0)) then
  104. innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width);
  105. innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height);
  106. innerContainer:setAnchorPoint(cc.p(0 , 0));
  107. end
  108. end
  109. me:doLayout();
  110. elseif eventType == cc.ScrollviewEventType.scrollToBottom or eventType == cc.ScrollviewEventType.scrollToRight then
  111. if me.tailDataIdx > #me.orderArray then
  112. return;
  113. end
  114. for i = 1 , me.loadCount do
  115. me:addItem(false);
  116. end
  117. if me.tailDataIdx > #me.orderArray and me.isWheelStyle and #me.scrollItems > 0 then
  118. me:fillEnd();
  119. end
  120. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  121. if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 0)) then
  122. innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width);
  123. innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height);
  124. innerContainer:setAnchorPoint(cc.p(0 , 0));
  125. end
  126. else
  127. if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 1)) then
  128. innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width);
  129. innerContainer:setPositionY(innerContainer:getPositionY() + (1 - innerContainer:getAnchorPoint().y) * innerContainer:getSize().height);
  130. innerContainer:setAnchorPoint(cc.p(0 , 1));
  131. end
  132. end
  133. me:doLayout();
  134. end
  135. if eventType == cc.ScrollviewEventType.scrolling then
  136. if me.isWheelStyle then
  137. me:onScrollWheel();
  138. end
  139. --[[ elseif eventType == cc.ScrollviewEventType.scrollingEnded then
  140. -- 自动回滚
  141. if me.isWheelStyle and me.autoScrollToFocus then
  142. local minDis;
  143. local minOffset
  144. for i,v in ipairs(me.scrollItems) do
  145. local node = v.layoutNode;
  146. local pos = me.scrollView:convertToNodeSpace2D(node:getWorldPosition());
  147. -- ui相对于scrollView的center坐标
  148. local center;
  149. local offset;
  150. local dis = 0;
  151. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  152. center = pos.x + node:getContentSize().width * (0.5 - node:getAnchorPoint().x);
  153. if center > me.scrollView:getSize().width or center < 0 then
  154. return;
  155. end
  156. offset = center - me.scrollView:getSize().width * me.focusPercent;
  157. dis = math.abs(offset);
  158. else
  159. center = pos.y + node:getContentSize().height * (0.5 - node:getAnchorPoint().y);
  160. if center > me.scrollView:getSize().height or center < 0 then
  161. return;
  162. end
  163. offset = center - me.scrollView:getSize().height * me.focusPercent;
  164. dis = math.abs(offset);
  165. end
  166. if not minDis then
  167. minDis = dis;
  168. minOffset = offset;
  169. return;
  170. end
  171. if dis < minDis then
  172. minDis = dis;
  173. minOffset = offset;
  174. end
  175. end;
  176. if #me.scrollItems > 0 and minDis > me.FocusRadius then
  177. local vec2 = cc.p(0 , 0);
  178. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  179. vec2.x = -minOffset / me.scrollDisScale;
  180. else
  181. vec2.y = -minOffset / me.scrollDisScale;
  182. end
  183. me.scrollView:scrollBy(vec2 , 0.5 / me.scrollDisScale , true);
  184. me:onScrollWheel();
  185. end
  186. end
  187. --]]
  188. end
  189. end);
  190. end
  191. function SmartScrollView:doLayout()
  192. self.scrollView:getInnerContainer():requestDoLayout();
  193. self.scrollView:getInnerContainer():doLayout();
  194. end
  195. function SmartScrollView:removeAllChildren()
  196. self.scrollView:removeAllChildren();
  197. self.scrollItems = {};
  198. end
  199. -- @param index 对应数组下标,从1开始
  200. function SmartScrollView:getUIItem(index)
  201. return self.scrollItems[index].showObj;
  202. end
  203. -- 将index 对应的ui 节点滚动到focus处,从1开始
  204. function SmartScrollView:scrollToIndex(index , onEnd)
  205. local me = self;
  206. if index < 1 or index > #me.orderArray then
  207. return;
  208. end
  209. if #me.scrollItems <= 0 then
  210. return;
  211. end
  212. while index <= me.headDataIdx or index >= me.tailDataIdx do
  213. me:addItem(index <= me.headDataIdx);
  214. end
  215. me:doLayout();
  216. local item = me.scrollItems[index].layoutNode;
  217. if not item then
  218. return;
  219. end
  220. local pos = me.scrollView:convertToNodeSpace2D(item:getWorldPosition());
  221. local center;
  222. local offset;
  223. local speed = 1800;
  224. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  225. center = pos.x + item:getContentSize().width * (0.5 - item:getAnchorPoint().x);
  226. offset = center - me.scrollView:getSize().width * me.focusPercent;
  227. me.scrollView:scrollBy(cc.p(-offset , 0) , math.max(offset / speed , 0.5) , true);
  228. else
  229. center = pos.y + item:getContentSize().height * (0.5 - item:getAnchorPoint().y);
  230. offset = center - me.scrollView:getSize().height * me.focusPercent;
  231. me.scrollView:scrollBy(cc.p(0 , -offset) , math.max(offset / speed , 0.5) , true);
  232. end
  233. if onEnd then
  234. me.scrollView:runDelay(math.max(offset / speed , 0.5) , onEnd);
  235. end
  236. end
  237. -- index从1开始
  238. function SmartScrollView:jumpToIndex(index)
  239. local me = self;
  240. local innerContainer = me.scrollView:getInnerContainer();
  241. if index < 1 and index > #me.orderArray then
  242. return;
  243. end
  244. if #me.scrollItems <= 0 then
  245. return;
  246. end
  247. while index <= me.headDataIdx or index >= me.tailDataIdx do
  248. me:addItem(index <= me.headDataIdx);
  249. end
  250. me:doLayout();
  251. local item = me.scrollItems[index].layoutNode;
  252. if not item or not item:isVisible() then
  253. me.scrollView:jumpToTop();
  254. return;
  255. end
  256. local pos = me.scrollView:convertToNodeSpace2D(item:getWorldPosition());
  257. local center;
  258. local offset;
  259. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  260. center = pos.x + item:getContentSize().width * (0.5 - item:getAnchorPoint().x);
  261. offset = center - me.scrollView:getSize().width * me.focusPercent;
  262. local finalPosX = innerContainer:getPositionX() - offset;
  263. innerContainer:setPositionX(finalPosX);
  264. local finalRightX = finalPosX + (1 - innerContainer:getAnchorPoint().x) * innerContainer:getSize().width;
  265. local finalLeftX = finalPosX - innerContainer:getAnchorPoint().x * innerContainer:getSize().width;
  266. if finalRightX < me.scrollView:getSize().width then
  267. innerContainer:setPositionX(finalPosX + me.scrollView:getSize().width - finalRightX);
  268. end
  269. if finalLeftX > 0 then
  270. innerContainer:setPositionX(finalPosX - finalLeftX);
  271. end
  272. else
  273. center = pos.y + item:getContentSize().height * (0.5 - item:getAnchorPoint().y);
  274. offset = center - me.scrollView:getSize().height * me.focusPercent;
  275. local finalPosY = innerContainer:getPositionY() - offset;
  276. innerContainer:setPositionY(finalPosY);
  277. local finalTopY = finalPosY + (1 - innerContainer:getAnchorPoint().y) * innerContainer:getSize().height;
  278. local finalBottomY = finalPosY - innerContainer:getAnchorPoint().y * innerContainer:getSize().height;
  279. if finalBottomY > 0 then
  280. innerContainer:setPositionY(finalPosY - finalBottomY);
  281. end
  282. if finalTopY < me.scrollView:getSize().height then
  283. innerContainer:setPositionY(finalPosY + me.scrollView:getSize().height - finalTopY);
  284. end
  285. end
  286. end
  287. -- orderArray更新后调用
  288. function SmartScrollView:onDataChanged()
  289. local me = self;
  290. me:removeAllChildren();
  291. -- 修正initIndex
  292. if me.initIndex < 1 then
  293. me.initIndex = 1;
  294. end
  295. if me.initIndex > #me.orderArray then
  296. me.initIndex = #me.orderArray;
  297. end
  298. -- 修正headDataIdx 的初始位置
  299. me.headDataIdx = me.initIndex + math.floor(me.initCount / 2);
  300. if me.headDataIdx < 1 then
  301. me.headDataIdx = 1;
  302. end
  303. if me.headDataIdx > #me.orderArray then
  304. me.headDataIdx = #me.orderArray;
  305. end
  306. local count = #me.orderArray > me.initCount and me.initCount or #me.orderArray;
  307. for i = 1 , count do
  308. me:addItem(true);
  309. end
  310. me.tailDataIdx = me.headDataIdx + #me.scrollItems + 1;
  311. if me.isWheelStyle and me.headDataIdx < 1 and #me.scrollItems > 0 then
  312. me:fillStart();
  313. end
  314. if me.isWheelStyle and me.tailDataIdx > #me.orderArray and #me.scrollItems > 0 then
  315. me:fillEnd();
  316. end
  317. me:doLayout();
  318. -- 第一次更新数据,播放动画
  319. if me.isPlayAnime and me.playAnimeOnce then
  320. me.playAnimeOnce = false;
  321. local delayTime = 0.5 --app.config.Rule.get("SCROLL_VIEW_ANI_DELAY_TIME").value;
  322. local playTime = 0.5 --app.config.Rule.get("SCROLL_VIEW_ANI_PLAY_TIME").value;
  323. local time = 0;
  324. for i = me.headDataIdx + 1 , me.tailDataIdx - 1 do
  325. local node = me.scrollItems[i].layoutNode;
  326. if node and node:isVisible() then
  327. time = time + delayTime;
  328. local offset = node:getContentSize().width;
  329. for ii,vv in pairs(node:getChildren()) do
  330. if vv:isVisible() then
  331. vv:setPositionX(vv:getPositionX() - offset);
  332. vv:runAction(cc.Sequence.create(cc.DelayTime:create(time)
  333. , cc.MoveBy:create(playTime , cc.vec3(offset,0,0))
  334. ));
  335. end
  336. end
  337. end
  338. end
  339. end
  340. me:jumpToIndex(me.initIndex);
  341. if me.isWheelStyle then
  342. me:onScrollWheel();
  343. end
  344. end
  345. -- 在开头填空
  346. function SmartScrollView:fillStart()
  347. local me = self;
  348. local node = me.scrollItems[1].layoutNode;
  349. local startNullNode = cc.Widget.create();
  350. startNullNode:setAutoSize(false);
  351. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  352. startNullNode:setSize(cc.size(me.scrollView:getSize().width * me.focusPercent - node:getContentSize().width / 2
  353. , node:getContentSize().height
  354. ));
  355. else
  356. startNullNode:setSize(cc.size(node:getContentSize().width
  357. , me.scrollView:getSize().height * (1 - me.focusPercent) - node:getContentSize().height / 2
  358. ));
  359. end
  360. startNullNode:setAutoSize(true);
  361. me.scrollView:addChild(startNullNode , -100);
  362. end
  363. -- 在结尾填空
  364. function SmartScrollView:fillEnd()
  365. local me = self;
  366. local node = me.scrollItems[#me.orderArray].layoutNode;
  367. local endNullNode = cc.Widget.create();
  368. endNullNode:setAutoSize(false);
  369. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  370. endNullNode:setSize(cc.size(
  371. me.scrollView:getSize().width * (1 - me.focusPercent) - node:getContentSize().width / 2 ,
  372. node:getContentSize().height
  373. ));
  374. else
  375. endNullNode:setSize(cc.size(
  376. node:getContentSize().width ,
  377. me.scrollView:getSize().height * me.focusPercent - node:getContentSize().height / 2
  378. ));
  379. end
  380. endNullNode:setAutoSize(true);
  381. me.scrollView:addChild(endNullNode , #me.orderArray + 100);
  382. end
  383. -- 滚轮效果
  384. function SmartScrollView:onScrollWheel()
  385. local me = self;
  386. for i,v in pairs(me.scrollItems) do
  387. local node = v.layoutNode;
  388. local pos = me.scrollView:convertToNodeSpace2D(node:getWorldPosition());
  389. -- ui相对于scrollView的center坐标
  390. local center;
  391. local dis;
  392. local range;
  393. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  394. center = pos.x + node:getContentSize().width * (0.5 - node:getAnchorPoint().x);
  395. local a = center > me.scrollView:getSize().width + node:getContentSize().width * 2;
  396. local b = center < -node:getContentSize().width * 2;
  397. if a or b then
  398. return;
  399. end
  400. dis = math.abs(center - me.scrollView:getSize().width * me.focusPercent);
  401. range = (me.scrollView:getSize().width + node:getContentSize().width) / 2 * me.radiusRate;
  402. else
  403. center = pos.y + node:getContentSize().height * (0.5 - node:getAnchorPoint().y);
  404. local a = center > me.scrollView:getSize().height + node:getContentSize().height * 2;
  405. local b = center < -node:getContentSize().height * 2;
  406. if a or b then
  407. return;
  408. end
  409. dis = math.abs(center - me.scrollView:getSize().height * me.focusPercent);
  410. range = (me.scrollView:getSize().height / 2 + node:getContentSize().height / 2) * me.radiusRate;
  411. end
  412. -- 可视范围内的
  413. local item = node:getChildren()[0];
  414. local percent = (range - dis) / range;
  415. if percent < 0.001 then
  416. percent = 0.001;
  417. end
  418. -- 置透明度
  419. item:setOpacity(255 * percent);
  420. -- 置缩放大小
  421. item:setScale(percent);
  422. -- 置渲染秩序
  423. node:setLocalZOrder(range - dis);
  424. end
  425. end
  426. function SmartScrollView:nextDataIdx(isAddToHead)
  427. local me = self;
  428. if isAddToHead then
  429. me.headDataIdx = me.headDataIdx - 1;
  430. else
  431. me.tailDataIdx = me.tailDataIdx + 1;
  432. end
  433. end
  434. -- @param isAddToHead 是否向数组小端取数据
  435. function SmartScrollView:addItem(isAddToHead)
  436. local me = self;
  437. local curDataIdx = isAddToHead and me.headDataIdx or me.tailDataIdx;
  438. if not me.orderArray[curDataIdx] then
  439. if curDataIdx >= 1 and curDataIdx <= #me.orderArray then
  440. me:nextDataIdx(isAddToHead);
  441. end
  442. return false;
  443. end
  444. assert(me.createUI , "SmartScrollView wasn't assigned ui create function!");
  445. local item = me:createUI(me.orderArray[curDataIdx], curDataIdx);
  446. if not item then
  447. me:nextDataIdx(isAddToHead);
  448. return false;
  449. end
  450. local ui = item.ui or item;
  451. local node = cc.Widget:create();
  452. node:setAutoSize(false);
  453. if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then
  454. node:setSize(cc.size(ui:getSize().width - me.coverRate , ui:getSize().height));
  455. ui:setPositionX(ui:getAnchorPoint().x * ui:getSize().width - me.coverRate / 2);
  456. ui:setPositionY(ui:getAnchorPoint().y * ui:getSize().height);
  457. else
  458. node:setSize(cc.size(ui:getSize().width , ui:getSize().height - me.coverRate));
  459. ui:setPositionX(ui:getAnchorPoint().x * ui:getSize().width);
  460. ui:setPositionY(ui:getAnchorPoint().y * ui:getSize().height - me.coverRate / 2);
  461. end
  462. -- 节点可见性
  463. local oldValue = ui:isVisible();
  464. ui:setVisible(true);
  465. ui.setVisible = function(this , flag)
  466. node:setVisible(flag);
  467. end
  468. ui.isVisible = function()
  469. return node:isVisible();
  470. end
  471. node:addChild(ui);
  472. node:setAutoSize(true);
  473. node:setVisible(oldValue);
  474. me.scrollView:addChild(node , curDataIdx);
  475. me.scrollItems[curDataIdx] = {layoutNode = node , showObj = item};
  476. me:nextDataIdx(isAddToHead);
  477. return true;
  478. end
  479. return SmartScrollView;