SuperScrollView.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import SuperListItem from "./SuperListItem";
  2. const { ccclass, property } = cc._decorator;
  3. @ccclass
  4. export default class SuperScrollView extends cc.ScrollView {
  5. @property({
  6. type: cc.Node,
  7. tooltip: 'item模板',
  8. })
  9. pfItemTemplate: cc.Node = null;
  10. itemSize: cc.Size = null;
  11. itemNodePool: cc.NodePool = null;
  12. @property({
  13. tooltip: '分帧加载时间间隔',
  14. })
  15. duration: number = 1;
  16. // @property({
  17. // tooltip: '最多可以显示多少个Item',
  18. // })
  19. visibleNum: number = 12;
  20. private curIndex: number = 0;
  21. private itemInfoList: any[] = [];
  22. private isFrameLoading: boolean = false;
  23. private isLoadingFinished: boolean = true;
  24. private cbAfterSetData: Function = null;
  25. private lastTime: number = 0;
  26. onLoad() {
  27. this.elastic = true;
  28. this.content.on(cc.Node.EventType.CHILD_ADDED, this.childAdded.bind(this));
  29. this.node.on("touchend", this.onTouchEnd, this);
  30. this.node.on("scroll-ended", this.onScrollEnded, this);
  31. this.node.on("scrolling", this.onScrolling, this);
  32. this.node.on("bounce-top", this.onBounceTop, this);
  33. this.node.on("bounce-bottom", this.onBounceBottom, this);
  34. this.node.on("bounce-left", this.onBounceLeft, this);
  35. this.node.on("bounce-right", this.onBounceRight, this);
  36. this.initNodePool();
  37. }
  38. onDestroy() {
  39. this.content.off(cc.Node.EventType.CHILD_ADDED, this.childAdded);
  40. this.node.off("touchend", this.onTouchEnd, this);
  41. this.node.off("scroll-ended-with-threshold", this.onScrollEnded, this);
  42. this.node.off("scrolling", this.onScrolling, this);
  43. this.node.off("bounce-top", this.onBounceTop, this);
  44. this.node.off("bounce-bottom", this.onBounceBottom, this);
  45. this.node.off("bounce-left", this.onBounceLeft, this);
  46. this.node.off("bounce-right", this.onBounceRight, this);
  47. }
  48. private initNodePool() {
  49. let itemNode = cc.instantiate(this.pfItemTemplate);
  50. let contentSize = itemNode.getContentSize();
  51. this.itemSize = contentSize;
  52. let parentSize = this.content.parent.getContentSize();
  53. let layoutComp = this.content.getComponent(cc.Layout);
  54. let num = 0;
  55. if (layoutComp) {
  56. if (layoutComp.type === cc.Layout.Type.VERTICAL && this.vertical && !this.horizontal) {
  57. num = Math.ceil(parentSize.height / contentSize.height);
  58. } else if (layoutComp.type === cc.Layout.Type.HORIZONTAL && this.horizontal && this.vertical) {
  59. num = Math.ceil(parentSize.width / contentSize.width);
  60. } else if (layoutComp.type === cc.Layout.Type.GRID) {
  61. let rowEleCount = Math.floor((parentSize.width - layoutComp.paddingLeft - layoutComp.paddingRight + layoutComp.spacingX) / (contentSize.width + layoutComp.spacingX))
  62. let colEleCount = Math.floor((parentSize.height - layoutComp.paddingTop - layoutComp.paddingBottom + layoutComp.spacingY) / (contentSize.height + layoutComp.spacingY))
  63. num = rowEleCount * colEleCount;
  64. // console.log('最多显示个数:GRID', rowEleCount, colEleCount);
  65. }
  66. }
  67. this.visibleNum = Math.floor(num * 2) + 2;
  68. // console.log('最多显示个数', this.visibleNum);
  69. if (this.itemNodePool) {
  70. //清空
  71. this.itemNodePool.clear();
  72. } else {
  73. this.itemNodePool = new cc.NodePool();
  74. }
  75. //多放3个
  76. for (let i = 0; i < this.visibleNum + 3; i++) {
  77. let itemNode = cc.instantiate(this.pfItemTemplate);
  78. this.itemNodePool.put(itemNode);
  79. }
  80. }
  81. //添加节点
  82. private childAdded(itemNode: cc.Node) {
  83. this.curIndex++;
  84. itemNode.name = "item" + this.curIndex;
  85. }
  86. public scrollToIndex(index: number, seconds: number = 0.2) {
  87. // cc.log("index:", index);
  88. let childCount = this.content.childrenCount;
  89. // cc.log("childCount:", childCount);
  90. if (index < 1) {
  91. // cc.log("index 过小");
  92. index = 1;
  93. }
  94. else if (index > childCount) {
  95. // cc.log("index 过大");
  96. index = childCount;
  97. }
  98. let item = this.content.getChildByName('item' + index);
  99. if (item) {
  100. let layoutComp: cc.Layout = this.content.getComponent(cc.Layout);
  101. if (layoutComp) {
  102. if (layoutComp.type === cc.Layout.Type.VERTICAL && this.vertical && !this.horizontal) {
  103. this.scrollToPercentVertical((childCount - index) / childCount, seconds);
  104. } else if (layoutComp.type === cc.Layout.Type.HORIZONTAL && !this.vertical && this.horizontal) {
  105. this.scrollToPercentHorizontal((childCount - index) / childCount, seconds);
  106. } else if (layoutComp.type === cc.Layout.Type.GRID) {
  107. if (layoutComp.startAxis === cc.Layout.AxisDirection.HORIZONTAL) {
  108. let contentSize = this.content.getContentSize();
  109. let itemContentSize = item.getContentSize();
  110. // contentSize.width = layoutComp.paddingLeft + layoutComp.paddingRight + itemContentSize.width * n + layoutComp.spacingX *(n-1)
  111. let rowEleCount = Math.floor((contentSize.width - layoutComp.paddingLeft - layoutComp.paddingRight + layoutComp.spacingX) / (itemContentSize.width + layoutComp.spacingX))
  112. // cc.log("每行多少个:", rowEleCount);
  113. let hang = Math.ceil(index / rowEleCount);
  114. let totalHang = Math.ceil(childCount / rowEleCount);
  115. this.scrollToPercentVertical((totalHang - hang) / totalHang, seconds);
  116. } else {
  117. let contentSize = this.content.getContentSize();
  118. let itemContentSize = item.getContentSize();
  119. let colEleCount = Math.floor((contentSize.height - layoutComp.paddingTop - layoutComp.paddingBottom + layoutComp.spacingY) / (itemContentSize.height + layoutComp.spacingY))
  120. // cc.log("每列多少个:", colEleCount);
  121. let lie = Math.ceil(index / colEleCount);
  122. let totalLie = Math.ceil(childCount / colEleCount);
  123. this.scrollToPercentHorizontal((totalLie - lie) / totalLie, seconds);
  124. }
  125. } else {
  126. // cc.log("cc.Layout.Type不对");
  127. }
  128. }
  129. }
  130. }
  131. private onTouchEnd() {
  132. return;
  133. this.improveDC();
  134. }
  135. private onScrollEnded() {
  136. // cc.log("onScrollEnded");
  137. this.improveDC();
  138. }
  139. private onScrolling() {
  140. let now = Date.now();
  141. if (now - this.lastTime < 200) {
  142. return;
  143. }
  144. // cc.log("scolling");
  145. this.lastTime = now;
  146. let scrollOffset = this.getScrollOffset();
  147. let offsetX = scrollOffset.x;
  148. let offsetY = scrollOffset.y;
  149. this.improveDC();
  150. }
  151. private onBounceTop() {
  152. // cc.log("onBounceTop")
  153. }
  154. private onBounceBottom() {
  155. // cc.log("onBounceBottom")
  156. }
  157. private onBounceLeft() {
  158. // cc.log("onBounceLeft")
  159. }
  160. private onBounceRight() {
  161. // cc.log("onBounceLeft")
  162. }
  163. private newItemNode(): cc.Node {
  164. // let itemNode = this.itemNodePool.get();
  165. // if (!itemNode) {
  166. // cc.log("instantiate");
  167. // itemNode = cc.instantiate(this.pfItemTemplate);
  168. // }
  169. let itemNode = cc.instantiate(this.pfItemTemplate);
  170. return itemNode;
  171. }
  172. // 优化DrawCall
  173. public improveDC() {
  174. if (this.content.childrenCount == 0) {
  175. return;
  176. }
  177. let svLeftBottomPoint: cc.Vec2 = this.node.parent.convertToWorldSpaceAR(
  178. // cc.v2(0,0)
  179. cc.v2(
  180. this.node.x - this.node.anchorX * this.node.width,
  181. this.node.y - this.node.anchorY * this.node.height
  182. )
  183. );
  184. // 求出 ScrollView 可视区域在世界坐标系中的矩形(碰撞盒)
  185. let svBBoxRect: cc.Rect = cc.rect(svLeftBottomPoint.x, svLeftBottomPoint.y, this.node.width, this.node.height);
  186. // 遍历 ScrollView Content 内容节点的子节点,对每个子节点的包围盒做和 ScrollView 可视区域包围盒做碰撞判断
  187. this.content.children.forEach((childNode: cc.Node) => {
  188. // 如果相交了,那么就显示,否则就隐藏
  189. let childNodeBBox = childNode.getBoundingBoxToWorld();
  190. if (childNodeBBox.intersects(svBBoxRect)) {
  191. if (childNode.opacity === 0) {
  192. childNode.opacity = 255;
  193. }
  194. } else {
  195. if (childNode.opacity !== 0) {
  196. childNode.opacity = 0;
  197. }
  198. }
  199. });
  200. }
  201. public async setData(itemInfoList: any[], isFrameLoading: boolean = false, cb?: Function) {
  202. if (!this.isLoadingFinished) {
  203. return;
  204. }
  205. this.curIndex = 0;
  206. this.isLoadingFinished = false;
  207. this.isFrameLoading = isFrameLoading;
  208. this.itemInfoList = itemInfoList;
  209. this.cbAfterSetData = cb;
  210. this.content.destroyAllChildren();
  211. if (this.isFrameLoading) {
  212. await this.executePreFrame(this.getItemGenerator(this.itemInfoList.length), this.duration);
  213. } else {
  214. for (let i = 0; i < this.itemInfoList.length; i++) {
  215. let item = this.newItemNode();
  216. item.parent = this.content;
  217. item.getComponent(SuperListItem).setData(itemInfoList[i]);
  218. }
  219. this.isLoadingFinished = true;
  220. this.scheduleOnce(() => {
  221. this.cbAfterSetData && this.cbAfterSetData();
  222. this.improveDC();
  223. });
  224. }
  225. }
  226. //@ts-ignore
  227. private executePreFrame(generator: Generator, duration: number) {
  228. return new Promise((resolve, reject) => {
  229. let gen = generator;
  230. // 创建执行函数
  231. let execute = () => {
  232. // 执行之前,先记录开始时间
  233. let startTime = new Date().getTime();
  234. // 然后一直从 Generator 中获取已经拆分好的代码段出来执行
  235. for (let iter = gen.next(); ; iter = gen.next()) {
  236. // 判断是否已经执行完所有 Generator 的小代码段,如果是的话,那么就表示任务完成
  237. if (iter == null || iter.done) {
  238. //@ts-ignore
  239. resolve();
  240. return;
  241. }
  242. // 每执行完一段小代码段,都检查一下是否已经超过我们分配的本帧,这些小代码端的最大可执行时间
  243. if (new Date().getTime() - startTime > duration) {
  244. // 如果超过了,那么本帧就不在执行,开定时器,让下一帧再执行
  245. this.scheduleOnce(() => {
  246. execute();
  247. });
  248. return;
  249. }
  250. }
  251. };
  252. // 运行执行函数
  253. execute();
  254. });
  255. }
  256. private initItem(itemInfo: any) {
  257. let itemNode = this.newItemNode();
  258. itemNode.parent = this.content;
  259. itemNode.getComponent(SuperListItem).setData(itemInfo);
  260. }
  261. private *getItemGenerator(length: number) {
  262. for (let i = 0; i < length; i++) {
  263. yield this.initItem(this.itemInfoList[i]);
  264. }
  265. this.isLoadingFinished = true;
  266. this.scheduleOnce(() => {
  267. this.cbAfterSetData && this.cbAfterSetData();
  268. this.improveDC();
  269. });
  270. }
  271. public canInputData(): boolean {
  272. return this.isLoadingFinished;
  273. }
  274. //通过index去获取节点
  275. public getItem(index: number): cc.Node {
  276. let item = this.content.getChildByName('item' + index);
  277. return item || null;
  278. }
  279. // update (dt) {}
  280. }