ListView.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import ListViewItem from "./ListViewItem";
  2. const {ccclass, property} = cc._decorator;
  3. /**
  4. * 实现scrollview item循环滚动 ,目前只支持垂直
  5. */
  6. /**
  7. * count: length of listview
  8. * listViewData: data of listview
  9. */
  10. type ListViewData = {
  11. count: number,
  12. listViewData: any
  13. }
  14. @ccclass
  15. export default class ListView extends cc.Component {
  16. // @property(cc.Label)
  17. // itemCountLabel: cc.Label = null;
  18. @property({
  19. type: cc.Prefab,
  20. tooltip: "listview单元格,仅支持大小相等的单元格"
  21. })
  22. prefabCell: cc.Prefab = null; // listview单元格,仅支持大小相等的单元格
  23. @property({
  24. type: cc.ScrollView,
  25. tooltip: "找到scrollview的控件"
  26. })
  27. scrollView:cc.ScrollView = null; // 找到scrollview的控件
  28. @property(cc.Integer)
  29. spacing: number = 0; // listview单元格间距
  30. // @property(cc.String)
  31. // cellScriptName: string = "";
  32. @property({
  33. type: cc.Node,
  34. tooltip: "找到scrollview的content"
  35. })
  36. content: cc.Node = null; // 找到scrollview的content, 搭配scrollview使用
  37. @property({
  38. type: cc.Integer,
  39. tooltip: "缓冲区总个数,即上下缓冲区分别bufferZoneCount / 2"
  40. })
  41. bufferZoneCount: number = 4; // 缓冲区总个数,即上下缓冲区分别bufferZoneCount / 2
  42. items: cc.Node[] = [];
  43. itemCount: number = 0;
  44. prefabCellHeight: number = 0;
  45. prefabCellWidth: number = 0;
  46. updateTimer: number = 0;
  47. updateInterval: number = 0.1;
  48. lastContentPosX: number = 0;
  49. lastContentPosY: number = 0;
  50. _bufferZone: number = 0; //when item is away from bufferZone, we relocate it
  51. _spawnCount: number = 0; // how many items we actually spawn
  52. _totalCount: number = 0; // how many items we need for the whole list
  53. _isUpdate: boolean = false;
  54. _itemData: any;
  55. // _listViewItem: ListViewItem = new ListViewItem();
  56. onLoad () {
  57. this._bufferZone = this.scrollView.node.height;
  58. this.content.anchorX = 0.5;
  59. this.content.anchorY = 1;
  60. if (this.scrollView.horizontal) {
  61. this._bufferZone = this.scrollView.node.width;
  62. this.content.anchorX = 0;
  63. this.content.anchorY = 0.5;
  64. }
  65. this.prefabCellHeight = this.prefabCell.data.height;
  66. this.prefabCellWidth = this.prefabCell.data.width;
  67. this.scrollView.node.on('scrolling', this.scrolling.bind(this), this);
  68. this.scrollView.node.on('scroll-ended', this.scrollEnded.bind(this), this);
  69. // this.init({count: 20, listViewData: null});
  70. }
  71. /**
  72. * 初始化ListView组件
  73. * @param listViewData ListViewData
  74. */
  75. public init (listViewData: ListViewData) {
  76. this.content.destroyAllChildren();
  77. this.items = [];
  78. this.lastContentPosY = 0;
  79. this.lastContentPosX = 0;
  80. this.itemCount = listViewData.count;
  81. this._itemData = listViewData.listViewData;
  82. if (this.scrollView.vertical && this.scrollView.horizontal) {
  83. return;
  84. }
  85. this._totalCount = this.itemCount;
  86. this._spawnCount = Math.floor(this._bufferZone / (this.prefabCellHeight + this.spacing)) + this.bufferZoneCount;
  87. if (this.scrollView.horizontal) {
  88. this._spawnCount = Math.floor(this._bufferZone / (this.prefabCellWidth + this.spacing)) + this.bufferZoneCount;
  89. }
  90. if (this._spawnCount > this._totalCount) {
  91. this._spawnCount = this._totalCount;
  92. }
  93. if (this.scrollView.vertical) {
  94. this.scrollView.scrollToTop();
  95. this.content.height = this._totalCount * (this.prefabCellHeight + this.spacing) + this.spacing; // get total content height
  96. } else if (this.scrollView.horizontal) {
  97. this.scrollView.scrollToLeft();
  98. this.content.width = this._totalCount * (this.prefabCellWidth + this.spacing) + this.spacing; // get total content width
  99. }
  100. for (let i = 0; i < this._spawnCount; ++i) { // spawn items, we only need to do this once
  101. let item = cc.instantiate(this.prefabCell);
  102. this.content.addChild(item);
  103. item.getComponent(ListViewItem).initData(i, this._itemData);
  104. if (this.scrollView.vertical) {
  105. item.setPosition(0, -item.height * (0.5 + i) - this.spacing * (i + 1));
  106. } else if (this.scrollView.horizontal) {
  107. item.setPosition(item.width * (i + 0.5) + this.spacing * (i + 1), 0);
  108. }
  109. this.items.push(item);
  110. }
  111. }
  112. /**
  113. * 获取item在scrollview中的位置
  114. * @param item item位置
  115. */
  116. private getPositionInView (item) { // get item position in scrollview's node space
  117. let worldPos = item.parent.convertToWorldSpaceAR(item.position);
  118. let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
  119. return viewPos;
  120. }
  121. /**
  122. * 监听:滚动时更新
  123. */
  124. private scrolling(){
  125. this._isUpdate = true;
  126. }
  127. /**
  128. * 监听:滚动停止时不执行逻辑
  129. */
  130. private scrollEnded() {
  131. this._isUpdate = false;
  132. }
  133. update (dt) {
  134. // this.itemCountLabel.string = (this.content.childrenCount).toString();
  135. // scrolling excute or don't excute
  136. if (!this._isUpdate) return;
  137. this.updateTimer += dt;
  138. if (this.updateTimer < this.updateInterval) return; // we don't need to do the math every frame
  139. this.updateTimer = 0;
  140. let items = this.items;
  141. let buffer = this._bufferZone;
  142. if (this.scrollView.vertical) {
  143. let isDown = this.scrollView.content.y < this.lastContentPosY; // scrolling direction
  144. let offset = (this.prefabCellHeight + this.spacing) * items.length;
  145. for (let i = 0; i < items.length; ++i) {
  146. let viewPos = this.getPositionInView(items[i]);
  147. let item = items[i].getComponent(ListViewItem);
  148. if (isDown) {
  149. // if away from buffer zone and not reaching top of content
  150. if (viewPos.y < -buffer && items[i].y + offset < 0) {
  151. items[i].y = items[i].y + offset;
  152. items[i].getComponent(ListViewItem).initData(item.itemId -items.length, this._itemData);
  153. }
  154. } else {
  155. // if away from buffer zone and not reaching bottom of content
  156. if (viewPos.y > buffer / 2 + (this.prefabCellHeight + this.spacing) / 2 && items[i].y - offset > -this.content.height) {
  157. items[i].y = items[i].y - offset;
  158. items[i].getComponent(ListViewItem).initData(item.itemId + items.length, this._itemData);
  159. }
  160. }
  161. }
  162. // update lastContentPosY
  163. this.lastContentPosY = this.scrollView.content.y;
  164. } else if (this.scrollView.horizontal) {
  165. let isRight = this.scrollView.content.x > this.lastContentPosX; // scrolling direction
  166. let offset = (this.prefabCellWidth + this.spacing) * items.length;
  167. for (let i = 0; i < items.length; ++i) {
  168. let viewPos = this.getPositionInView(items[i]);
  169. if (isRight) {
  170. // if away from buffer zone and not reaching top of content
  171. if (viewPos.x > buffer / 2 + (this.prefabCellWidth + this.spacing) / 2 && items[i].x - offset > 0) {
  172. items[i].x = items[i].x - offset;
  173. }
  174. } else {
  175. // if away from buffer zone and not reaching bottom of content
  176. if (viewPos.x < -buffer / 2 - (this.prefabCellWidth + this.spacing) / 2 && items[i].x + offset < this.content.width) {
  177. items[i].x = items[i].x + offset;
  178. }
  179. }
  180. }
  181. // update lastContentPosX
  182. this.lastContentPosX = this.scrollView.content.x;
  183. }
  184. }
  185. };