• 【ccc3.8】虚拟列表


    使用cocos creator 3.8 版本做的一个简单的虚拟列表,滚动到指定下标外没有任何其他东西。

    原理就是向上滚动时,将下面离开屏幕的那一个item塞到上侧来:

    主代码仅有两个:ScrollList对应的滚动容器,ScrollListItem对应单项的预制体

    当前支持两种:竖向滚动、横线滚动。不支持那种横竖排版的,有需要自己添加

    有一个功能就是滚动到指定下标的数据

    使用案例DEMO:https://github.com/EddyLwei/ccc3.8_VirtualList.git

    里面有这两个横竖的案例

    ScrollList:

    1. /**横向排布拖动*/
    2. export const SCROLL_HORIZONTAL: number = 1;
    3. /**竖向排布拖动*/
    4. export const SCROLL_VERTICAL: number = 2;
    5. import { _decorator, Node, Prefab, instantiate, ScrollView, UITransform, Vec3, log } from 'cc';
    6. import { ScrollListItem } from './ScrollListItem';
    7. const { ccclass, property } = _decorator;
    8. @ccclass('ScrollList')
    9. export class ScrollList extends ScrollView {
    10. /**item子节点预制体*/
    11. @property({ type: Prefab, tooltip: "item子节点预制体" })
    12. itemPrefab: Prefab = null;
    13. /**单条记录高度*/
    14. private _itemSize: number;
    15. /**需要多少个记录组件 在可视范围内+2条*/
    16. private _numItem: number = 0;
    17. private _itemArr: Array<Node> = [];
    18. /**开始端下标*/
    19. private _itemIndex: number = 0;
    20. /**结束端下标*/
    21. private _dataIndex: number = 0;
    22. /**数据源*/
    23. private _dataArr: any[];
    24. /**滚动方向*/
    25. private _direction: number = 0;
    26. /**间隙 0=开始边框,1=结束边框,2=间隙*/
    27. private _gapNum: number[];
    28. /**子节点刷新绑定事件,或者使用item继承的模式*/
    29. public onItemRender: Function;
    30. start() {
    31. super.start();
    32. this.node.on('scrolling', this.scrollCheck, this);
    33. }
    34. onDestroy() {
    35. super.onDestroy();
    36. if (this.node) {
    37. this.node.off('scrolling', this.scrollCheck, this);
    38. }
    39. }
    40. /**设置数据
    41. * @param dataArr : 数据源
    42. * @param direction : 滚动方向,默认上下
    43. * @param gap : [开始边框距离,结束边框距离,每个之间空隙]
    44. */
    45. public setDataList(dataArr: any[], direction: number = SCROLL_VERTICAL, gap?: number[]) {
    46. this._dataArr = dataArr;
    47. this._direction = direction;
    48. this._gapNum = gap;
    49. this.createItem();
    50. }
    51. /**获得数据后开始创建*/
    52. private createItem() {
    53. let _showSize = this.node.getComponent(UITransform).height;
    54. //获得预制体的高度
    55. if (!this._itemSize) {
    56. let pNode = instantiate(this.itemPrefab);
    57. if (this._direction == SCROLL_HORIZONTAL) {
    58. this._itemSize = pNode.getComponent(UITransform).contentSize.width;
    59. _showSize = this.node.getComponent(UITransform).width;
    60. }
    61. else {
    62. this._itemSize = pNode.getComponent(UITransform).contentSize.height;
    63. }
    64. pNode.destroy();
    65. // log("---_itemSize--", this._itemSize);
    66. }
    67. //可视范围,对应可以创建多少个实体单例item
    68. this._numItem = Math.floor(_showSize / this._itemSize) + 2;
    69. log(_showSize, "初始化获得数量:", this._numItem)
    70. if (this._dataArr.length < this._numItem) {
    71. this._numItem = this._dataArr.length;
    72. }
    73. this._itemArr.length = 0;
    74. for (let index = 0; index < this._numItem; index++) {
    75. let pNode = instantiate(this.itemPrefab);
    76. pNode.parent = this.content;
    77. this._itemArr.push(pNode);
    78. this.itemRender(pNode, index);
    79. }
    80. //设置容器大小
    81. let contentSize = this._itemSize * this._dataArr.length;
    82. //前面距离边框
    83. if (this._gapNum && this._gapNum[0]) {
    84. contentSize += this._gapNum[0];
    85. }
    86. //后面距离边框
    87. if (this._gapNum && this._gapNum[1]) {
    88. contentSize += this._gapNum[1];
    89. }
    90. //间隙距离
    91. if (this._gapNum && this._gapNum[2]) {
    92. contentSize += this._gapNum[2] * (this._dataArr.length - 1);
    93. }
    94. if (this._direction == SCROLL_HORIZONTAL) {
    95. this.content.getComponent(UITransform).width = contentSize;
    96. }
    97. else {
    98. this.content.getComponent(UITransform).height = contentSize;
    99. }
    100. this._itemIndex = this._dataIndex = this._itemArr.length - 1;
    101. log("初始化结束:", this._dataIndex, this._itemArr.length)
    102. }
    103. private scrollCheck() {
    104. let nowPos = this.getScrollOffset().y;
    105. let topPos = (this._dataIndex + 1 - this._numItem) * this._itemSize;//当前屏幕中靠近最开始的坐标
    106. //前面边框
    107. if (this._gapNum && this._gapNum[0]) {
    108. topPos += this._gapNum[0];
    109. }
    110. //间隙距离
    111. if (this._gapNum && this._gapNum[2]) {
    112. topPos += this._gapNum[2] * (this._dataIndex + 1 - this._numItem);
    113. }
    114. // let topPos = this.countPosByIndex(this._dataIndex + 1 - this._numItem);
    115. let size = this._itemSize;
    116. if (this._direction == SCROLL_HORIZONTAL) {
    117. nowPos = this.getScrollOffset().x;
    118. topPos = -topPos;
    119. size = -this._itemSize;
    120. }
    121. //判断向结束端滚动,滚动点和初始点对比
    122. if ((this._direction == SCROLL_VERTICAL && nowPos > size + topPos) ||
    123. (this._direction == SCROLL_HORIZONTAL && nowPos < size + topPos)) {
    124. let newIndex = this._dataIndex + 1;
    125. // Log.log(this._dataIndex, "-判断向结束端滚动 1 --将头部item转移到最后---", nowPos, topPos);
    126. if (newIndex >= this._dataArr.length) {
    127. return; //如果滚动到底部最后一条数据,不再进行写入
    128. }
    129. this._dataIndex = newIndex;
    130. let topItemIndex = this._itemIndex + 1;
    131. if (topItemIndex >= this._numItem) {
    132. topItemIndex = 0;
    133. }
    134. let item = this._itemArr[topItemIndex];
    135. if (item) {
    136. this.itemRender(item, newIndex);
    137. // Log.error(topItemIndex, "转移到最后", item.node.position);
    138. }
    139. this._itemIndex = topItemIndex;
    140. }
    141. //判断向开始端滚动
    142. else if ((this._direction == SCROLL_VERTICAL && nowPos < topPos) ||
    143. (this._direction == SCROLL_HORIZONTAL && nowPos > topPos)) {
    144. let newIndex = this._dataIndex + 1 - this._numItem - 1;
    145. // Log.log(this._dataIndex, "-判断向上滚动 2 -将最后item转移到头部----", newIndex);
    146. if (newIndex < 0) {
    147. // Log.warn("如果滚动到第一条数据,不再进行写入", newIndex)
    148. return; //如果滚动到第一条数据,不再进行写入
    149. }
    150. this._dataIndex--;
    151. // Log.error(this._itemIndex, "将最后item转移到头部", this._dataIndex, newIndex, newIndex * -this._itemSize);
    152. let item = this._itemArr[this._itemIndex];
    153. if (item) {
    154. this.itemRender(item, newIndex);
    155. // Log.error(this._itemIndex, "转移头部", item.node.position);
    156. }
    157. this._itemIndex--;
    158. if (this._itemIndex < 0) {
    159. this._itemIndex = this._numItem - 1;
    160. }
    161. }
    162. }
    163. /**刷新单项*/
    164. private itemRender(node: Node, newIndex: number) {
    165. //设置有全局得刷新事件
    166. if (this.onItemRender) {
    167. this.onItemRender(node, newIndex);
    168. }
    169. //没有全局,使用继承的item
    170. else {
    171. const item = node.getComponent(ScrollListItem)
    172. if (item) {
    173. item.onItemRender(this._dataArr[newIndex]);
    174. }
    175. }
    176. this.setPos(node, newIndex);
    177. }
    178. /**设置坐标*/
    179. private setPos(node: Node, index: number) {
    180. let pos = this.countPosByIndex(index);
    181. if (this._direction == SCROLL_HORIZONTAL) {
    182. node.setPosition(new Vec3(pos, 0));
    183. }
    184. else {
    185. node.setPosition(new Vec3(0, -pos));
    186. }
    187. }
    188. /**根据下标计算坐标。 0 ~ length-1*/
    189. private countPosByIndex(index: number): number {
    190. let pos = (1 / 2 + index) * this._itemSize;
    191. //前面距离边框
    192. if (this._gapNum && this._gapNum[0]) {
    193. pos += this._gapNum[0];
    194. }
    195. //间隙距离
    196. if (this._gapNum && this._gapNum[2]) {
    197. pos += this._gapNum[2] * index;
    198. }
    199. return pos;
    200. }
    201. /**滚动到指定下标*/
    202. public scroll2Index(index: number) {
    203. this.stopAutoScroll();
    204. //太靠近结束点,需要回退屏幕显示数量
    205. if (index > this._dataArr.length - this._numItem - 2) {
    206. index = this._dataArr.length - this._numItem - 2;
    207. }
    208. if (index < 0) {
    209. index = 0;
    210. }
    211. /**设置滚动坐标*/
    212. let pos = this.countPosByIndex(index) - 1 / 2 * this._itemSize;
    213. let ve = new Vec2(0, -pos);
    214. if (this._direction == SCROLL_HORIZONTAL) {
    215. ve = new Vec2(pos, 0);
    216. }
    217. this.scrollToOffset(ve);//滚动
    218. for (let x = 0; x < this._itemArr.length; x++) {
    219. this.itemRender(this._itemArr[x], index + x);
    220. }
    221. this._dataIndex = this._itemArr.length - 1 + index;//数据下标
    222. this._itemIndex = this._itemArr.length - 1;//重新赋值后节点下标为数组内当前最大
    223. }
    224. }

    ScrollListItem:(其实仅用一个接口的作用)

    1. import { _decorator, Component, } from 'cc';
    2. const { ccclass, property } = _decorator;
    3. @ccclass('ScrollListItem')
    4. export class ScrollListItem extends Component {
    5. /**滚动列表数据变更*/
    6. onItemRender(data, ...param: any[]) { }
    7. }

    使用方式很简单:

    1. import { _decorator, Component, Node } from 'cc';
    2. import { SCROLL_HORIZONTAL, SCROLL_VERTICAL, ScrollList } from './ScrollList';
    3. const { ccclass, property } = _decorator;
    4. @ccclass('TestScene')
    5. export class TestScene extends Component {
    6. @property({ type: ScrollList, tooltip: "竖行滚动容器" })
    7. private vScroll: ScrollList;
    8. @property({ type: ScrollList, tooltip: "横向滚动容器" })
    9. private hScroll: ScrollList;
    10. start() {
    11. const dataArr = [];
    12. for (let index = 0; index < 50; index++) {
    13. dataArr.push(index)
    14. }
    15. this.hScroll.setDataList(dataArr, SCROLL_HORIZONTAL, [50, 50, 20]);
    16. this.vScroll.setDataList(dataArr, SCROLL_VERTICAL, [50, 50, 20]);
    17. }
    18. }

    组件的设置注意:

  • 相关阅读:
    实验2 创建数据集(一)
    将十进制转化成其他进制‘支持2-16进制(C语言)昨天码完忘发了,呜呜呜
    看了我的mybatis-plus用法,同事也开始悄悄模仿了
    openssl版本升级
    AlexNet网络结构详解与花分类数据集下载
    康耐视visionpro定位引导标定简介及方法
    适合企业的TTS文本转语音接口:微软TTS最新模型,发布9种更真实的AI语音
    abc 137 Count 1‘s 【经典思维】
    8、常用基本命令(重要)
    码住!听我说护眼台灯这样选!
  • 原文地址:https://blog.csdn.net/EddyLwei/article/details/134018624