LoopList.ts 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. import { Node, Component, Vec2, Size, Prefab, instantiate, v3, ScrollView, v2, size, Tween, tween, UITransform, Vec3, sys, UIRenderer } from "cc";
  2. import RenderAlternative from "./RenderAlternative";
  3. /**边缘 */
  4. interface IBorder {
  5. /**节点左边缘x世界坐标 */
  6. leftSide?: number;
  7. /**节点右边缘x世界坐标 */
  8. rightSide?: number;
  9. /**节点下边缘y世界坐标 */
  10. bottomSide?: number;
  11. /**节点上边缘y世界坐标 */
  12. topSide?: number;
  13. /**大小 */
  14. size?: Size;
  15. }
  16. type TDate = { index?: number, size?: Size };
  17. export class ShowNode<T extends TDate> extends Component {
  18. private hasInit: boolean = false;
  19. /**渲染分层开启的挂载节点 */
  20. public renderLayer: Node;
  21. /**上级的cell */
  22. public cell: ItemCell<T>;
  23. /**数据 */
  24. public data: T;
  25. /**初始化 */
  26. public init(cell: ItemCell<T>, data: T, renderLayer: Node) {
  27. this.cell = cell;
  28. this.data = data;
  29. if (this.renderLayer != renderLayer) {
  30. this.renderLayer = renderLayer;
  31. renderLayer && this.initRenders();
  32. }
  33. this.initShow();
  34. }
  35. /**初始化操作 */
  36. public initShow() { }
  37. /**被回收,应该做一些释放的操作 */
  38. public onRecircle() { }
  39. /**
  40. * 在特定时间内进行大小变化
  41. * @param targetSize
  42. * @param time
  43. */
  44. public resize(targetSize: Size, time: number = 0.3) {
  45. this.cell.resize(targetSize, time);
  46. }
  47. /**
  48. * 处理分层代理渲染
  49. */
  50. private initRenders() {
  51. if (!this.hasInit && this.renderLayer) {
  52. let propMap: WeakMap<RenderAlternative, boolean> = new WeakMap();
  53. let keys = Object.keys(this);
  54. for (let i = 0, len = keys.length; i < len; ++i) {
  55. let value = this[keys[i]];
  56. let alternative: RenderAlternative = null;
  57. if (value instanceof Node) {
  58. alternative = value.getComponent(RenderAlternative);
  59. if (alternative) {
  60. alternative.init(this.renderLayer);
  61. }
  62. } else if (value instanceof UIRenderer) {
  63. alternative = value.getComponent(RenderAlternative);
  64. if (alternative) {
  65. this[keys[i]] = alternative.init<typeof value>(this.renderLayer);
  66. }
  67. }
  68. alternative && propMap.set(alternative, true);
  69. }
  70. this.dealChilrenAlternative(this.node, propMap, 0, 0);
  71. this.hasInit = true;
  72. }
  73. }
  74. /**
  75. * 处理子节点代理
  76. * @param node
  77. * @param propMap 记录脚本已经处理过的属性代理
  78. * @param parentSindex 父节点的所在的顺序
  79. * @param depth 深度
  80. */
  81. dealChilrenAlternative(node: Node, propMap: WeakMap<RenderAlternative, boolean>, parentSindex: number, depth: number) {
  82. for (let i = 0, len = node.children.length; i < len; i++) {
  83. let children = node.children[i];
  84. let alternative = children.getComponent(UIRenderer)?.getComponent(RenderAlternative);
  85. let sIndex = parentSindex + (i * Math.pow(0.1, depth));
  86. if (alternative) {
  87. if ((!propMap.has(alternative) || !propMap.get(alternative))) {
  88. alternative.init(this.renderLayer);
  89. }
  90. alternative.sIndex = sIndex;
  91. }
  92. this.dealChilrenAlternative(children, propMap, sIndex, depth + 1);
  93. }
  94. }
  95. }
  96. export class ItemCell<T extends TDate> {
  97. /**持有cell的list */
  98. public looplist: LoopList<T> = null;
  99. /**大小处理 */
  100. public uiTrans: UITransform = null;
  101. /**位置节点 */
  102. public node: Node = null;
  103. /**显示节点 */
  104. public showNode: ShowNode<T> = null;
  105. /**数据下标 */
  106. public dataIndex: number = null;
  107. /**cell下标 */
  108. public cellIndex: number = null;
  109. /**尺寸 */
  110. public size: Size = null;
  111. /**数据 */
  112. public data: T = null;
  113. /**
  114. * 构造函数
  115. * @param node
  116. * @param looplist
  117. */
  118. constructor(node: Node, looplist: LoopList<T>) {
  119. this.node = node;
  120. this.looplist = looplist;
  121. }
  122. /**
  123. * 初始化
  124. * @param node 节点
  125. * @param data 数据
  126. * @param size 大小
  127. * @param dataIndex 数据下标
  128. * @param cellIndex 节点下标
  129. * @param looplist 挂载的列表
  130. */
  131. public init(data: T, size: Size, dataIndex: number, cellIndex: number) {
  132. Tween.stopAllByTarget(this.node);
  133. this.cellIndex = cellIndex;
  134. this.dataIndex = dataIndex;
  135. !this.uiTrans && (this.uiTrans = this.node.getComponent(UITransform));
  136. this.uiTrans.width = size.width;
  137. this.uiTrans.height = size.height;
  138. this.data = data;
  139. this.size = size;
  140. }
  141. /**
  142. * 重置大小
  143. * @param newSize
  144. * @param time
  145. */
  146. resize(newSize: Size, time: number) {
  147. let nodeSides = getNodeBorder(this, [true, true, true, true]);
  148. let changeSize = size(newSize.width - this.size.width, newSize.height - this.size.height);
  149. this.size = newSize;
  150. this.looplist.dealChangeSize(this, nodeSides, changeSize, time).then(() => {
  151. this.uiTrans.width = newSize.width;
  152. this.uiTrans.height = newSize.height;
  153. this.looplist.updateNodes();
  154. });
  155. }
  156. /**
  157. *
  158. * 更新节点显示
  159. * @param isShow 是否显示
  160. * @param prefab 需要显示的节点的预制件(也可以是节点,或者预制件url,生成方式自由改动)
  161. * @param cacheNode 对象池中的显示节点
  162. * @param renderLayer 分层节点
  163. */
  164. updateShow(isShow: boolean, prefab: Prefab | Node, cacheNode: ShowNode<T>, renderLayer: Node) {
  165. if (isShow) {
  166. //当前没有显示节点
  167. if (!this.showNode) {
  168. //如果是临时对象池还有节点则重用
  169. if (cacheNode) {
  170. this.showNode = cacheNode;
  171. } else {//否则使用传入的预制件进行生成
  172. this.showNode = (instantiate(prefab) as Node).getComponent(ShowNode<T>);
  173. }
  174. this.showNode.node.setParent(this.node);
  175. this.showNode.node.position = v3();
  176. this.updateShowNode(renderLayer);
  177. }
  178. }
  179. }
  180. /**
  181. * 更新showNode节点显示
  182. * @param renderLayer
  183. */
  184. updateShowNode(renderLayer: Node) {
  185. //当前有显示节点
  186. if (this.showNode) {
  187. this.showNode.init(this, this.data, renderLayer);
  188. }
  189. }
  190. /**是否有显示节点 */
  191. get hasNoNode() {
  192. return !this.showNode;
  193. }
  194. /**回收显示节点 */
  195. recircle() {
  196. if (this.showNode) {
  197. let node = this.showNode;
  198. this.showNode.cell = null;
  199. this.showNode.onRecircle();
  200. this.showNode.node.removeFromParent();
  201. this.showNode = null;
  202. return node;
  203. }
  204. }
  205. destroy() {
  206. this.node.destroy();
  207. this.node = null;
  208. this.uiTrans = null;
  209. this.looplist = null;
  210. }
  211. /**
  212. * 失去显示节点
  213. * @returns
  214. */
  215. lostShowNode() {
  216. if (this.showNode) {
  217. let node = this.showNode;
  218. this.showNode.cell = null;
  219. this.showNode.node.removeFromParent();
  220. this.showNode = null;
  221. return node;
  222. }
  223. }
  224. /**
  225. * 获取显示节点
  226. * @param showNode
  227. */
  228. getShowNode(showNode: ShowNode<T>) {
  229. this.showNode = showNode;
  230. showNode.node.setParent(this.node);
  231. this.showNode.cell = this;
  232. }
  233. }
  234. /**
  235. * 目前列表注意事项
  236. * 1.开启cellMaxNum后不能一行多列或者一列多行,只能单行或者单列
  237. */
  238. export default class LoopList<T extends TDate> {
  239. public static insMap: Map<string, LoopList<any>> = new Map();
  240. public static ins<T>(listName: string): LoopList<T> {
  241. if (!this.insMap.has(listName)) {
  242. this.insMap.set(listName, new LoopList<T>());
  243. }
  244. return this.insMap.get(listName) as LoopList<T>;
  245. }
  246. /**分层节点(只有在showNode中挂载了RenderAlternative才能起作用) */
  247. private renderLayer: Node = null;
  248. /**滚动组件 */
  249. private scrollView: ScrollView = null;
  250. /**cell节点最大数量 */
  251. private cellMaxNum: number = null;
  252. /**当前起点 */
  253. private curStartIndex: number = null;
  254. /**容器的ui控件 */
  255. private contentUiTrans: UITransform = null;
  256. /**数据 */
  257. private datas: T[] = [];
  258. /**item节点们 */
  259. private itemCells: ItemCell<T>[] = [];
  260. /**预制件 */
  261. private prefab: Prefab | Node = null;
  262. /**显示节点的池子 */
  263. private showNodePool: ShowNode<T>[] = [];
  264. /**边缘数据 */
  265. private viewSides: IBorder = {};
  266. /**上一帧滚动容器的坐标 */
  267. private lastPos: Vec3 = null;
  268. /**刷新显示的间隔次数 */
  269. public updateInv: number = 0;
  270. /**循环切换的最后一次时间 */
  271. private lastChange: number = 0;
  272. /**通用尺寸 */
  273. public size: Size = null;
  274. /**通用锚点 */
  275. public anchor: Vec2 = null;
  276. /**
  277. * 更新边界
  278. */
  279. public updateBorder() {
  280. this.contentUiTrans = this.scrollView.content.getComponent(UITransform);
  281. let view = this.scrollView.view;
  282. view.anchorX = this.contentUiTrans.anchorX;
  283. view.anchorY = this.contentUiTrans.anchorY;
  284. //修改锚点之后下一帧世界坐标才会更新,没办法只能等一帧了
  285. return new Promise<void>((resolve, reject) => {
  286. requestAnimationFrame(() => {
  287. let wpos = view.node.worldPosition.clone();
  288. //显示区域左边缘
  289. let leftSide = wpos.x - (view.width * view.anchorX);
  290. //显示区域右边缘
  291. let rightSide = wpos.x + (view.width * (1 - view.anchorX));
  292. //显示区域下边缘
  293. let bottomSide = wpos.y - (view.height * view.anchorY);
  294. //显示区域上边缘
  295. let topSide = wpos.y + (view.height * (1 - view.anchorY));
  296. this.viewSides = {
  297. leftSide, rightSide, bottomSide, topSide, size: size(view.width, view.height)
  298. };
  299. this.contentUiTrans.node.setPosition(v3(0, 0));
  300. resolve();
  301. })
  302. })
  303. }
  304. /**
  305. * 初始化滚动列表*
  306. * @param scrollView
  307. * @param renderLayer 渲染分层的父节点,如果传入则可以配合showNode上的renderAlternative来进行渲染分层
  308. * @param cellMaxNum cell节点的最大数量,设置成null则不启用,启用的话,要设置成(可视窗刚好能显示的最大数量*2)+2,假设最多能看到10个节点,那么设置成22
  309. */
  310. async init(scrollView: ScrollView, renderLayer?: Node, cellMaxNum?: number) {
  311. cellMaxNum && (this.cellMaxNum = cellMaxNum);
  312. renderLayer && (this.renderLayer = renderLayer);
  313. this.scrollView = scrollView;
  314. await this.updateBorder();
  315. this.scrollView.node.on("scrolling", this.scrollingListener, this);
  316. }
  317. /**滚动监听 */
  318. scrollingListener() {
  319. this.updateInv--;
  320. if (this.updateInv <= 0) {
  321. this.updateInv = 2;
  322. let showCells = this.updateNodes();
  323. if (!this.cellMaxNum) {
  324. return;
  325. }
  326. if (!this.lastPos) {
  327. this.lastPos = this.scrollView.content.position.clone();
  328. } else {
  329. let offset = this.scrollView.content.position.clone().subtract(this.lastPos);
  330. this.lastPos = this.scrollView.content.position.clone();
  331. let now = sys.now();
  332. if (!this.lastChange || (now - this.lastChange) > 1) {
  333. this.lastChange = now;
  334. this.dealLoopList(offset, showCells);
  335. }
  336. }
  337. }
  338. }
  339. /**
  340. * 初始化
  341. * @param data 数据组
  342. * @param prefab 预制件
  343. * @param size 通用尺寸
  344. * @param anchor cell的锚点
  345. */
  346. public initData(data: T[], prefab: Prefab | Node, size?: Size, anchor: Vec2 = v2(0.5, 0.5)) {
  347. this.datas = data;
  348. this.size = size;
  349. this.anchor = anchor;
  350. this.prefab = prefab;
  351. let len = this.cellMaxNum ? this.cellMaxNum : data.length;
  352. for (let i = 0; i < len; ++i) {
  353. let data = this.datas[this.curStartIndex + i];
  354. if (!data) continue;
  355. let cellSize = data.size ? data.size : size;
  356. let itemCell = this.itemCells[i];
  357. if (!itemCell) {
  358. let node = new Node('LoopListCell');
  359. let uiTrans = node.addComponent(UITransform);
  360. itemCell = this.itemCells[i] = new ItemCell<T>(node, this);
  361. uiTrans.setAnchorPoint(anchor);
  362. node.setParent(this.scrollView.content);
  363. }
  364. itemCell.init(data, cellSize, this.curStartIndex + i, i);
  365. }
  366. for (let i = data.length, len = this.itemCells.length; i < len; ++i) {
  367. this.recircleShowNode(this.itemCells[i].recircle());
  368. this.itemCells[i].destroy()
  369. this.itemCells[i] = null;
  370. }
  371. requestAnimationFrame(() => {
  372. this.updateNodes();
  373. })
  374. }
  375. /**
  376. * 压入新的数据
  377. * @param datas
  378. */
  379. public pushDatas(datas: T[]) {
  380. let length = this.datas.length;
  381. for (let i = 0, len = datas.length; i < len; ++i) {
  382. let data = datas[i];
  383. this.datas.push(data);
  384. let index = length + i;
  385. this.addNodeFromIndex(index);
  386. }
  387. }
  388. /**
  389. * 插入新的数据
  390. * @param data
  391. * @param insertIndex
  392. */
  393. public insertData(data: T, insertIndex: number) {
  394. if (this.datas.length >= insertIndex) {
  395. this.datas.splice(insertIndex, 0, data);
  396. this.addNodeFromIndex(insertIndex);
  397. } else {
  398. this.pushDatas([data]);
  399. }
  400. }
  401. /**
  402. * 移除对应数据
  403. * @param removeIndex
  404. */
  405. public removeData(removeIndex: number) {
  406. if (this.datas.length >= removeIndex) {
  407. this.datas.splice(removeIndex, 1);
  408. if (this.cellMaxNum) {
  409. let cellIndex = removeIndex - this.curStartIndex;
  410. //在使用范围内
  411. if (cellIndex >= 0 && cellIndex < this.cellMaxNum) {
  412. let itemCell = this.itemCells[cellIndex];
  413. let nodeSides = getNodeBorder(itemCell, [true, true, true, true]);
  414. let changeSize = size(-itemCell.size.width, -itemCell.size.height);
  415. itemCell.size = size(0, 0);
  416. this.dealChangeSize(itemCell, nodeSides, changeSize).then(() => {
  417. //更新数据
  418. for (let i = cellIndex; i < this.cellMaxNum; i++) {
  419. let curDataIndex = this.curStartIndex + i;
  420. let curData = this.datas[curDataIndex];
  421. let curCellSize: Size = curData.size ? curData.size : this.size;
  422. this.itemCells[i].init(curData, curCellSize, curDataIndex, i);
  423. this.itemCells[i].updateShowNode(this.renderLayer);
  424. }
  425. });
  426. }
  427. } else {
  428. let itemCell = this.itemCells.splice(removeIndex, 1)[0];
  429. let nodeSides = getNodeBorder(itemCell, [true, true, true, true]);
  430. let changeSize = size(-itemCell.size.width, -itemCell.size.height);
  431. itemCell.size = size(0, 0);
  432. this.dealChangeSize(itemCell, nodeSides, changeSize).then(() => {
  433. this.recircleShowNode(itemCell.recircle())
  434. itemCell.node.destroy();
  435. });
  436. }
  437. }
  438. }
  439. /**
  440. * 从某个数据开始更新
  441. * @param dataIndex
  442. */
  443. private async addNodeFromIndex(dataIndex: number) {
  444. let data = this.datas[dataIndex];
  445. let cellSize: Size = data.size ? data.size : this.size;
  446. //是否开启cell循环
  447. if (this.cellMaxNum) {
  448. let cellIndex = dataIndex - this.curStartIndex;
  449. //在使用范围内
  450. if (cellIndex >= 0 && cellIndex < this.cellMaxNum) {
  451. //更新数据
  452. for (let i = cellIndex; i < this.cellMaxNum; i++) {
  453. let curDataIndex = this.curStartIndex + i;
  454. let curData = this.datas[curDataIndex];
  455. let curCellSize: Size = curData.size ? curData.size : this.size;
  456. this.itemCells[i].init(curData, curCellSize, curDataIndex, i);
  457. this.itemCells[i].updateShowNode(this.renderLayer);
  458. }
  459. //获取当前的位置
  460. let nodeSides: IBorder = null;
  461. let itemCell: ItemCell<T> = this.itemCells[cellIndex];
  462. if (itemCell) {
  463. nodeSides = getNodeBorder(itemCell, [true, true, true, true]);
  464. itemCell.uiTrans.width = 0;
  465. itemCell.uiTrans.height = 0;
  466. this.dealChangeSize(itemCell, nodeSides, cellSize).then(() => {
  467. itemCell.uiTrans.width = cellSize.width;
  468. itemCell.uiTrans.height = cellSize.height;
  469. });
  470. }
  471. }
  472. } else {
  473. //获取当前的位置
  474. let nodeSides: IBorder = null;
  475. if (this.itemCells[dataIndex]) {
  476. nodeSides = getNodeBorder(this.itemCells[dataIndex], [true, true, true, true]);
  477. }
  478. let node = new Node('LoopListCell')
  479. let uiTrans = node.addComponent(UITransform);
  480. uiTrans.setAnchorPoint(this.anchor);
  481. node.setParent(this.scrollView.content);
  482. node.setSiblingIndex(dataIndex);
  483. let itemCell: ItemCell<T> = new ItemCell<T>(node, this);
  484. this.itemCells.splice(dataIndex, 0, itemCell)
  485. itemCell.init(data, cellSize, dataIndex, dataIndex);
  486. if (!nodeSides) {
  487. await new Promise<void>((resolve, reject) => {
  488. requestAnimationFrame(() => {
  489. nodeSides = getNodeBorder(itemCell, [true, true, true, true]);
  490. resolve();
  491. })
  492. });
  493. }
  494. if (nodeSides) {
  495. itemCell.uiTrans.width = 0;
  496. itemCell.uiTrans.height = 0;
  497. this.dealChangeSize(itemCell, nodeSides, cellSize).then(() => {
  498. itemCell.uiTrans.width = cellSize.width;
  499. itemCell.uiTrans.height = cellSize.height;
  500. });
  501. }
  502. this.dealItemShow(itemCell);
  503. }
  504. }
  505. /**
  506. * 处理列表的循环利用
  507. * @param offset
  508. * @param dataIndexs
  509. */
  510. private dealLoopList(offset: Vec3, showCells: ItemCell<T>[]) {
  511. let startCell = showCells[0];
  512. let endCell = showCells[showCells.length - 1];
  513. if (this.scrollView.vertical) {
  514. /**内容上移 */
  515. if (offset.y > 0) {
  516. //已经到达最后倒数第二个节点并且还有数据
  517. if (this.scrollView.content.position.y
  518. >= (- this.itemCells[this.itemCells.length - 2].node.position.y - this.viewSides.size.height) && endCell.dataIndex < this.datas.length - 2) {
  519. //显示开始数据下标(这里-1是想要顶部留一个itemcell做缓冲)
  520. let showStart = startCell.dataIndex - 1;
  521. //剩余可展示的数据量
  522. let leave = this.datas.length - showStart;
  523. //计算应该从那个位置开始重新赋值,因为如果直接移动到最上方,数据量不够覆盖所有的cell
  524. let cellStartIndex = Math.max(0, this.itemCells.length - leave);
  525. //计算第一个cell的数据下标
  526. this.curStartIndex = showStart - cellStartIndex;
  527. let cellTop = getNodeBorder(this.itemCells[startCell.cellIndex], [true]).topSide;
  528. let contentHeight = 0;
  529. let targetY = 0;
  530. let jumpFirst = true;
  531. let showIndex = 0;
  532. for (let i = 0, len = this.itemCells.length; i < len; ++i) {
  533. if (i >= cellStartIndex) {
  534. //将当前已经显示的showNode节点直接重新设置到移动后的节点上
  535. if (showIndex < showCells.length && !jumpFirst) {
  536. if (showIndex == 0) {
  537. targetY = contentHeight;
  538. }
  539. let showNode = showCells[showIndex].lostShowNode();
  540. showIndex++;
  541. this.itemCells[i].getShowNode(showNode);
  542. }
  543. jumpFirst = false;
  544. }
  545. let dataIndex = this.curStartIndex + i;
  546. let data = this.datas[dataIndex];
  547. let cellSize = data.size ? data.size : this.size;
  548. contentHeight += cellSize.height;
  549. this.itemCells[i].init(data, cellSize, dataIndex, i);
  550. }
  551. //将content的位置移动到最上方并对齐移动前的状态
  552. let posY = targetY + (cellTop - this.viewSides.topSide)
  553. this.scrollView.content.setPosition(v3(this.scrollView.content.position.x, posY));
  554. this.scrollView.scrollToOffset(v2(0, posY + endCell.size.height), 1)
  555. this.lastPos = null;
  556. }
  557. } //内容下移
  558. else if (offset.y < 0) {
  559. //已经到达第二个节点并且还有数据
  560. if (this.scrollView.content.position.y <
  561. (- this.itemCells[2].node.position.y) && startCell.dataIndex > 2) {
  562. //展示的最后一个数据下表,+1是为了在底部预留一部分空间
  563. let showEndIndex = endCell.dataIndex + 1;
  564. //计算应该从那个位置开始重新赋值,因为如果直接移动到最下方,数据量不够覆盖所有的cell
  565. let cellEndIndex = Math.min(this.itemCells.length - 2, showEndIndex - 1);
  566. //计算第一个cell的数据下标
  567. this.curStartIndex = Math.max(showEndIndex - (this.itemCells.length - 1), 0);
  568. //将content的位置移动到最下方并对齐移动前的状态
  569. let cellTop = getNodeBorder(this.itemCells[Math.max(startCell.cellIndex - 1, 0)], [true]).topSide;
  570. let contentHeight = 0;
  571. let showIndex = showCells.length - 1;
  572. for (let i = this.itemCells.length - 1; i >= 0; --i) {
  573. if (i <= cellEndIndex) {
  574. //将当前已经显示的showNode节点直接重新设置到移动后的节点上
  575. if (showIndex >= 0) {
  576. let showNode = showCells[showIndex].lostShowNode();
  577. showIndex--;
  578. this.itemCells[i].getShowNode(showNode);
  579. } else {
  580. showIndex--;
  581. }
  582. }
  583. let dataIndex = this.curStartIndex + i;
  584. let data = this.datas[dataIndex];
  585. let cellSize = data.size ? data.size : this.size;
  586. if (showIndex < -1) {
  587. contentHeight += cellSize.height;
  588. }
  589. this.itemCells[i].init(data, cellSize, dataIndex, i);
  590. }
  591. //将content的位置移动到最下方并对齐移动前的状态
  592. let posY = contentHeight + (cellTop - this.viewSides.topSide);
  593. this.scrollView.content.setPosition(v3(this.scrollView.content.position.x, posY));
  594. this.scrollView.scrollToOffset(v2(0, posY - startCell.size.height), 1)
  595. this.lastPos = null;
  596. }
  597. }
  598. } else if (this.scrollView.horizontal) {
  599. if (offset.x < 0) {
  600. //已经到达最后倒数第二个节点并且还有数据
  601. if (this.scrollView.content.position.x <=
  602. (- (this.itemCells[this.itemCells.length - 2].node.position.x - this.viewSides.size.width)) && endCell.dataIndex < this.datas.length - 2) {
  603. //显示开始数据下标(这里-1是想要左侧留一个itemcell做缓冲)
  604. let showStart = startCell.dataIndex - 1;
  605. //剩余可展示的数据量
  606. let leave = this.datas.length - showStart;
  607. //计算应该从那个位置开始重新赋值,因为如果直接移动到最左侧,数据量不够覆盖所有的cell
  608. let cellStartIndex = Math.max(0, this.itemCells.length - leave);
  609. //计算第一个cell的数据下标
  610. this.curStartIndex = showStart - cellStartIndex;
  611. let cellLeft = getNodeBorder(this.itemCells[startCell.cellIndex], [false, false, true]).leftSide;
  612. let contentWidth = 0;
  613. let targetX = 0;
  614. let jumpFirst = true;
  615. let showIndex = 0;
  616. for (let i = 0, len = this.itemCells.length; i < len; ++i) {
  617. if (i >= cellStartIndex) {
  618. //将当前已经显示的showNode节点直接重新设置到移动后的节点上
  619. if (showIndex < showCells.length && !jumpFirst) {
  620. if (showIndex == 0) {
  621. targetX = contentWidth;
  622. }
  623. let showNode = showCells[showIndex].lostShowNode();
  624. showIndex++;
  625. this.itemCells[i].getShowNode(showNode);
  626. }
  627. jumpFirst = false;
  628. }
  629. let dataIndex = this.curStartIndex + i;
  630. let data = this.datas[dataIndex];
  631. let cellSize = data.size ? data.size : this.size;
  632. contentWidth += cellSize.width;
  633. this.itemCells[i].init(data, cellSize, dataIndex, i);
  634. }
  635. //将content的位置移动到最左侧并对齐移动前的状态
  636. let posX = (cellLeft - this.viewSides.leftSide) - targetX;
  637. this.scrollView.content.setPosition(v3(posX, this.scrollView.content.position.y));
  638. this.scrollView.scrollToOffset(v2(-(posX - endCell.size.width), 0), 1)
  639. this.lastPos = null;
  640. }
  641. } else if (offset.x > 0) {
  642. //已经到达第二个节点并且还有数据
  643. if (this.scrollView.content.position.x >
  644. (-this.itemCells[2].node.position.x) && startCell.dataIndex > 2) {
  645. //展示的最后一个数据下表,+1是为了在右侧预留一部分空间
  646. let showEndIndex = endCell.dataIndex + 1;
  647. //计算应该从那个位置开始重新赋值,因为如果直接移动到最右侧,数据量不够覆盖所有的cell
  648. let cellEndIndex = Math.min(this.itemCells.length - 2, showEndIndex - 1);
  649. //计算第一个cell的数据下标
  650. this.curStartIndex = Math.max(showEndIndex - (this.itemCells.length - 1), 0);
  651. //将content的位置移动到最右侧并对齐移动前的状态
  652. let cellLeft = getNodeBorder(this.itemCells[Math.max(startCell.cellIndex - 1, 0)], [false, false, true]).leftSide;
  653. let contentWidth = 0;
  654. let showIndex = showCells.length - 1;
  655. for (let i = this.itemCells.length - 1; i >= 0; --i) {
  656. if (i <= cellEndIndex) {
  657. //将当前已经显示的showNode节点直接重新设置到移动后的节点上
  658. if (showIndex >= 0) {
  659. let showNode = showCells[showIndex].lostShowNode();
  660. showIndex--;
  661. this.itemCells[i].getShowNode(showNode);
  662. } else {
  663. showIndex--;
  664. }
  665. }
  666. let dataIndex = this.curStartIndex + i;
  667. let data = this.datas[dataIndex];
  668. let cellSize = data.size ? data.size : this.size;
  669. if (showIndex < -1) {
  670. contentWidth += cellSize.width;
  671. }
  672. this.itemCells[i].init(data, cellSize, dataIndex, i);
  673. }
  674. //将content的位置移动到最右侧并对齐移动前的状态
  675. let posX = (cellLeft - this.viewSides.leftSide) - contentWidth;
  676. this.scrollView.content.setPosition(v3(posX, this.scrollView.content.position.y));
  677. this.scrollView.scrollToOffset(v2(- (posX + startCell.size.width), 0), 1)
  678. this.lastPos = null;
  679. }
  680. }
  681. }
  682. }
  683. /**更新节点的显示 */
  684. public updateNodes() {
  685. let showCells: ItemCell<T>[] = [];
  686. for (let i = 0, len = this.itemCells.length; i < len; ++i) {
  687. let itemCell = this.itemCells[i];
  688. let isShow = this.dealItemShow(itemCell)
  689. if (isShow) {
  690. showCells.push(itemCell)
  691. }
  692. }
  693. return showCells;
  694. }
  695. /**
  696. * 处理cell的显示隐藏
  697. * @param itemCell
  698. */
  699. private dealItemShow(itemCell: ItemCell<T>) {
  700. if (itemCell) {
  701. let isShow = this.checkInView(itemCell);
  702. if (isShow) {
  703. itemCell.hasNoNode && itemCell.updateShow(isShow, this.prefab, this.showNodePool.length > 0 ? this.showNodePool.pop() : null, this.renderLayer);
  704. } else {
  705. !itemCell.hasNoNode && this.recircleShowNode(itemCell.recircle());
  706. }
  707. return isShow;
  708. }
  709. }
  710. /**
  711. * 检查是否在屏幕内
  712. * @param node
  713. * @returns
  714. */
  715. private checkInView(itemCell: ItemCell<T>) {
  716. let nodeSides = getNodeBorder(itemCell, [true, true, true, true]);
  717. //节点右侧边缘在屏幕左侧外面或者节点左边缘在屏幕右侧外面 则不在显示区域
  718. let horizontalOutside = nodeSides.rightSide < this.viewSides.leftSide
  719. || nodeSides.leftSide > this.viewSides.rightSide;
  720. let verticalOutside = false;
  721. if (!horizontalOutside) {
  722. //节点上边缘在屏幕下方外面或者节点下边缘在屏幕上方外面 则不在显示区域
  723. verticalOutside = nodeSides.topSide < this.viewSides.bottomSide
  724. || nodeSides.bottomSide > this.viewSides.topSide;
  725. }
  726. return !horizontalOutside && !verticalOutside;
  727. }
  728. /**
  729. * 处理变化的大小
  730. * @param cell
  731. * @param nodeSides
  732. * @param changeSize
  733. */
  734. public async dealChangeSize(cell: ItemCell<T>, nodeSides: IBorder, changeSize: Size, time: number = 0.3) {
  735. let topToBottomFulfill = (this.contentUiTrans.anchorY == 1 && nodeSides.bottomSide >= this.viewSides.topSide);
  736. let bottomToTopFulfill = (this.contentUiTrans.anchorY == 0 && nodeSides.topSide <= this.viewSides.bottomSide);
  737. if (topToBottomFulfill || bottomToTopFulfill) {
  738. if (topToBottomFulfill) {
  739. this.scrollView.content.position.add(v3(0, changeSize.height));
  740. } else {
  741. this.scrollView.content.position.subtract(v3(0, changeSize.height));
  742. }
  743. }
  744. let leftToRightFulfill = (this.contentUiTrans.anchorX == 0 && nodeSides.rightSide <= this.viewSides.leftSide);
  745. let RightToLeftFulfill = (this.contentUiTrans.anchorX == 1 && nodeSides.leftSide >= this.viewSides.rightSide);
  746. if (leftToRightFulfill || RightToLeftFulfill) {
  747. if (RightToLeftFulfill) {
  748. this.scrollView.content.position.add(v3(changeSize.width, 0));
  749. } else {
  750. this.scrollView.content.position.subtract(v3(changeSize.width, 0));
  751. }
  752. }
  753. if (!(topToBottomFulfill || bottomToTopFulfill || leftToRightFulfill || RightToLeftFulfill)) {
  754. Tween.stopAllByTarget(cell.node);
  755. await new Promise<void>((resolve, reject) => {
  756. tween(cell.uiTrans).to(time, { width: cell.size.width, height: cell.size.height }).call(() => {
  757. resolve();
  758. }).start();
  759. })
  760. }
  761. this.updateNodes();
  762. return true;
  763. }
  764. /**
  765. * 回收显示节点
  766. * @param node
  767. */
  768. private recircleShowNode(node: ShowNode<T>) {
  769. node && this.showNodePool.push(node);
  770. }
  771. /**
  772. * 释放当前列表
  773. */
  774. public freeList() {
  775. this.scrollView.node.off("scrolling", this.scrollingListener);
  776. for (let i = 0, len = this.itemCells.length; i < len; ++i) {
  777. this.recircleShowNode(this.itemCells[i].recircle());
  778. this.itemCells[i].destroy();
  779. }
  780. this.itemCells = [];
  781. for (let index = 0, len = this.showNodePool.length; index < len; ++index) {
  782. this.showNodePool[index].destroy();
  783. }
  784. this.showNodePool = [];
  785. this.renderLayer = null;
  786. this.scrollView = null;
  787. this.prefab = null;
  788. }
  789. }
  790. /**
  791. * 获取节点的边缘
  792. * @param cell
  793. * @param needArr 顺序是上 下 左 右
  794. */
  795. function getNodeBorder(cell: ItemCell<any>, needArr: boolean[]) {
  796. let borderData: IBorder = {};
  797. let wpos = cell.node.worldPosition.clone();
  798. //显示区域上边缘
  799. if (needArr[0]) {
  800. borderData.topSide = wpos.y + (cell.uiTrans.height * (1 - cell.uiTrans.anchorY));
  801. }
  802. //显示区域下边缘
  803. if (needArr[1]) {
  804. borderData.bottomSide = wpos.y - (cell.uiTrans.height * cell.uiTrans.anchorY);
  805. }
  806. //显示区域左边缘
  807. if (needArr[2]) {
  808. borderData.leftSide = wpos.x - (cell.uiTrans.width * cell.uiTrans.anchorX);
  809. }
  810. //显示区域右边缘
  811. if (needArr[3]) {
  812. borderData.rightSide = wpos.x + (cell.uiTrans.width * (1 - cell.uiTrans.anchorX));
  813. }
  814. borderData.size = size(cell.uiTrans.width, cell.uiTrans.height);
  815. return borderData;
  816. }