123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /**
- * 列表UI(单行或者单列)
- * 主要功能:
- * 1、支持方向选项:从左到右、从右到左、从上到下、从下到上
- * 2、实现了对象缓存池功能
- * 3、支持不同尺寸的单元格排列(需要重载 itemIndexToPrefabIndex 方法)
- * 4、支持单元格间距
- * 5、支持前后边距
- */
- cc.Class({
- extends: cc.Component,
- editor: {
- menu: '嘉米公用/JMListView'
- },
- properties: {
- itemPrefabList: {
- default: [],
- type: cc.Prefab,
- tooltip: '单元格预制体列表,默认仅取第一个。设定为列表是为了子类自定义列表——含有不同样式的单元格的列表。'
- },
- direction: {
- default: JMC.LIST_VIEW_DIRECTION.TOP_TO_BOTTOM, // 默认从上到下
- type: JMC.LIST_VIEW_DIRECTION,
- tooltip: '列表单元格排列的方向'
- },
- spacing: {
- default: 0,
- tooltip: '单元格间距'
- },
- paddingStart: {
- default: 0,
- tooltip: '开始边距'
- },
- paddingEnd: {
- default: 0,
- tooltip: '结束边距'
- }
- },
- onDestroy () {
- if (this.nodePoolList) {
- for (let v of this.nodePoolList) {
- v.clear();
- }
- }
- },
- init () {
- if (this._didInit) {
- return;
- }
- this._didInit = true;
- this._initNodePool();
- this._initScrollView();
- this._resetParams();
- },
- /**
- * 刷新数据源
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {List<any>} dataSource 数据列表,每个数据会传给 Item 刷新
- */
- reloadData (dataSource) {
- this.init();
- this.dataSource = dataSource;
- this.reloadView();
- },
- /**
- * 刷新 UI。会清理已展示的数据
- *
- * @author Pyden
- * @date 2019-03-21
- */
- reloadView () {
- this._initDataSource();
- this._resetShowParams(this.scrollView.getScrollOffset());
- },
- /**
- * Item 序号 转 预制体序号
- * 单元格是用不同预制体的时候,子类自定义该函数:确定单元格序号和预制体的关系。
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} itemIndex Item 序号。从0开始
- * @returns {int} 预制体序号
- */
- itemIndexToPrefabIndex (itemIndex) {
- return 0;
- },
- /**
- * 用预制体新建 Item
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {cc.Node}
- */
- newItem (index) {
- let prefab = this._getPrefab(index);
- let item = cc.instantiate(prefab);
- return item;
- },
- // ---------------- 私有方法分割线 ----------------
- /**
- * 初始化对象池
- *
- * @author Pyden
- * @date 2019-03-21
- */
- _initNodePool () {
- // 对象池列表。一个预制体对应一个对象池
- this.nodePoolList = [];
- let length = this.itemPrefabList.length;
- for (let i = 0; i < length; i++) {
- this.nodePoolList[i] = new cc.NodePool();
- }
- },
- /**
- * 初始化 ScrollView
- *
- * @author Pyden
- * @date 2019-03-21
- */
- _initScrollView () {
- this.scrollView = this.getComponent(cc.ScrollView);
- // 根据列表方向设置 ScrollView
- switch (this.direction) {
- case JMC.LIST_VIEW_DIRECTION.LEFT_TO_RIGHT:
- // 对齐 LEFT_CENTER
- this.scrollView.horizontal = true;
- this.scrollView.vertical = false;
- this.scrollView.content.anchorX = 0;
- this.scrollView.content.anchorY = 0.5;
- break;
- case JMC.LIST_VIEW_DIRECTION.RIGHT_TO_LEFT:
- // 对齐 RIGHT_CENTER
- this.scrollView.horizontal = true;
- this.scrollView.vertical = false;
- this.scrollView.content.anchorX = 1;
- this.scrollView.content.anchorY = 0.5;
- break;
- case JMC.LIST_VIEW_DIRECTION.TOP_TO_BOTTOM:
- // 对齐 TOP_CENTER
- this.scrollView.horizontal = false;
- this.scrollView.vertical = true;
- this.scrollView.content.anchorX = 0.5;
- this.scrollView.content.anchorY = 1;
- break;
- case JMC.LIST_VIEW_DIRECTION.BOTTOM_TO_TOP:
- // 对齐 BOTTOM_CENTER
- this.scrollView.horizontal = false;
- this.scrollView.vertical = true;
- this.scrollView.content.anchorX = 0.5;
- this.scrollView.content.anchorY = 0;
- break;
- default:
- break;
- }
- this.scrollView.node.on('scrolling', this._onScrolling, this);
- },
- /**
- * 重置参数
- *
- * @author Pyden
- * @date 2019-03-21
- */
- _resetParams () {
- this._startPosList = [];
- this._itemMap = {};
- this._oldMinIndex = -1;
- this._oldMaxIndex = -1;
- this._minIndex = -1;
- this._maxIndex = -1;
- },
- /**
- * 获取指定序号的预制体
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {cc.Prefab}
- */
- _getPrefab (index) {
- return this.itemPrefabList[this.itemIndexToPrefabIndex (index)];
- },
- /**
- * 获取指定序号的对象池
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {cc.NodePool}
- */
- _getNodePool (index) {
- return this.nodePoolList[this.itemIndexToPrefabIndex (index)];
- },
- /**
- * 获取指定序号的Item。优先从 NodePool 获取 Item,不足时才新建 Item
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {cc.Node}
- */
- _getItem (index) {
- let node = this._getItemFromPool(index);
- if (node) {
- return node;
- } else {
- return this.newItem(index);
- }
- },
- /**
- * 从 NodePool 获取 Item
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {cc.Node}
- */
- _getItemFromPool (index) {
- let nodePool = this._getNodePool(index);
- if (nodePool.size() > 0) {
- return nodePool.get();
- } else {
- return undefined;
- }
- },
- /**
- * 将 Item 回收到 NodePool。同时会从界面移除
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @param {cc.Node} item 回收的节点
- */
- _putItemToPool (index, item) {
- let nodePool = this._getNodePool(index);
- nodePool.put(item);
- },
- /**
- * 获取指定序号 Item 在列表方向上的长度
- * 如:从左到右的列表,返回单元格的宽度
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- * @returns {float} 长度
- */
- _getItemLength (index) {
- let ret = 0;
- // 根据列表方向取长度
- let prefab = this._getPrefab(index);
- if (
- this.direction == JMC.LIST_VIEW_DIRECTION.LEFT_TO_RIGHT ||
- this.direction == JMC.LIST_VIEW_DIRECTION.RIGHT_TO_LEFT
- ) {
- ret = prefab.data.width;
- } else {
- ret = prefab.data.height;
- }
- return ret;
- },
- /**
- * 初始化数据源确定的参数
- * this._startPosList: 各个 item 的起始坐标
- * this._count: Item 总数
- * this._viewLength: ListView 可视长度。水平方向的列表取宽度,垂直方向的列表取高度
- * this._contentLength: content 总长度
- *
- * @author Pyden
- * @date 2019-03-21
- */
- _initDataSource () {
- // 移除旧的数据
- if (this._oldMinIndex >= 0) {
- for (let i = this._oldMinIndex; i <= this._oldMaxIndex; i++) {
- this._removeItem(i);
- }
- }
- // 初始化参数
- this._resetParams();
- let count = this.dataSource.length;
- let viewLength;
- let contentLength;
- // content长度 = paddingStart + itemLength + spacing + ... + itemLength + spacing + paddingEnd
- contentLength = this.paddingStart;
- for (let index = 0; index < count; index++) {
- this._startPosList[index] = contentLength;
- let itemLength = this._getItemLength (index);
- // 仅最后一个不加间隔
- if (index == count - 1) {
- contentLength = contentLength + itemLength;
- } else {
- contentLength = contentLength + itemLength + this.spacing;
- }
- }
- contentLength = contentLength + this.paddingEnd;
- // 根据列表方向 取可视长度,设置内容长度
- if (this.direction == JMC.LIST_VIEW_DIRECTION.LEFT_TO_RIGHT || this.direction == JMC.LIST_VIEW_DIRECTION.RIGHT_TO_LEFT) {
- viewLength = this.scrollView.node.width;
- this.scrollView.content.width = Math.max(contentLength, viewLength);
- } else {
- viewLength = this.scrollView.node.height;
- this.scrollView.content.height = Math.max(contentLength, viewLength);
- }
- this._count = count;
- this._viewLength = viewLength;
- this._contentLength = contentLength;
- },
- /**
- * 重置需要展示内容的参数:minIndex、maxIndex。同时更新 Item
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {cc.Vec2} offset 当前的ScrollView的偏移
- */
- _resetShowParams (offset) {
- let count = this._count;
- let contentLength = this._contentLength;
- let viewLength = this._viewLength;
- let minPos = 0;
- let maxPos = 0;
- // 根据列表方向 获取可视范围
- switch (this.direction) {
- case JMC.LIST_VIEW_DIRECTION.LEFT_TO_RIGHT:
- // 对齐LEFT_CENTER
- minPos = -offset.x;
- maxPos = -offset.x + viewLength;
- break;
- case JMC.LIST_VIEW_DIRECTION.RIGHT_TO_LEFT:
- // 对齐RIGHT_CENTER
- minPos = contentLength + offset.x - viewLength;
- maxPos = contentLength + offset.x;
- break;
- case JMC.LIST_VIEW_DIRECTION.TOP_TO_BOTTOM:
- // 对齐TOP_CENTER
- minPos = offset.y;
- maxPos = offset.y + viewLength;
- break;
- case JMC.LIST_VIEW_DIRECTION.BOTTOM_TO_TOP:
- // 对齐BOTTOM_CENTER
- minPos = contentLength - offset.y - viewLength;
- maxPos = contentLength - offset.y;
- break;
- default:
- break;
- }
- let minIndex = count - 1;
- let maxIndex = count - 1;
- let index = 0;
- // 先得出minIndex
- for (; index < count - 1; index++) {
- let startPos = this._startPosList[index];
- if (startPos >= minPos) {
- minIndex = index;
- break;
- }
- let startPosNext = this._startPosList[index + 1];
- if (startPosNext > minPos) {
- minIndex = index;
- break;
- }
- }
- // 再得出maxIndex
- for (; index < count - 1; index++) {
- let startPos = this._startPosList[index];
- let startPosNext = this._startPosList[index + 1];
- if (startPos < maxPos && maxPos <= startPosNext) {
- maxIndex = index;
- break;
- }
- }
- this._minIndex = minIndex;
- this._maxIndex = maxIndex;
- // 更新Item
- this._updateItem();
- },
- /**
- * 更新 Item:移除刚刚不可见的,展示刚刚可见的
- *
- * @author Pyden
- * @date 2019-03-21
- */
- _updateItem () {
- let minIndex = this._minIndex;
- let maxIndex = this._maxIndex;
- let oldMinIndex = this._oldMinIndex;
- let oldMaxIndex = this._oldMaxIndex;
- // 保留下来不修改的Item序号范围
- let saveMinIndex = Math.max(minIndex, oldMinIndex);
- let saveMaxIndex = Math.min(maxIndex, oldMaxIndex);
- // 先移除
- for (let i = oldMinIndex; i < saveMinIndex; i++) {
- this._removeItem(i);
- }
- for (let i = saveMaxIndex + 1; i <= oldMaxIndex; i++) {
- this._removeItem(i);
- }
- // 再添加
- for (let i = minIndex; i < saveMinIndex; i++) {
- this._addItem(i);
- }
- for (let i = saveMaxIndex + 1; i <= maxIndex; i++) {
- this._addItem(i);
- }
- this._oldMinIndex = minIndex;
- this._oldMaxIndex = maxIndex;
- },
- /**
- * 添加指定需要的 Item 到界面
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- */
- _addItem (index) {
- let item = this._getItem(index);
- item.parent = this.scrollView.content;
- // 根据列表方向设置坐标
- switch (this.direction) {
- case JMC.LIST_VIEW_DIRECTION.LEFT_TO_RIGHT:
- // 对齐 LEFT_CENTER
- item.x = this._startPosList[index] + item.width * item.anchorX;
- item.y = item.height * (item.anchorY - 0.5);
- break;
- case JMC.LIST_VIEW_DIRECTION.RIGHT_TO_LEFT:
- // 对齐 RIGHT_CENTER
- item.x = -this._startPosList[index] - item.width * (1 - item.anchorX);
- item.y = item.height * (item.anchorY - 0.5);
- break;
- case JMC.LIST_VIEW_DIRECTION.TOP_TO_BOTTOM:
- // 对齐 TOP_CENTER
- item.x = item.width * (item.anchorX - 0.5);
- item.y = -this._startPosList[index] - item.height * (1 - item.anchorY);
- break;
- case JMC.LIST_VIEW_DIRECTION.BOTTOM_TO_TOP:
- // 对齐 BOTTOM_CENTER
- item.x = item.width * (item.anchorX - 0.5);
- item.y = this._startPosList[index] + item.height * item.anchorY;
- break;
- default:
- break;
- }
- // 刷新单元格数据
- let listViewItem = item.getComponent('JMListViewItem');
- let params = this.dataSource[index];
- listViewItem.reloadData(index, params);
- // 缓存
- this._itemMap[index] = item;
- },
- /**
- * 移除指定需要的 Item。同时会回收到 NodePool
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {int} index 序号。从0开始
- */
- _removeItem (index) {
- this._putItemToPool(index, this._itemMap[index]);
- this._itemMap[index] = undefined;
- },
- /**
- * 滚动监听
- *
- * @author Pyden
- * @date 2019-03-21
- * @param {cc.ScrollView} sender 滚动视图组件
- */
- _onScrolling (sender) {
- this._resetShowParams(sender.getScrollOffset());
- }
- });
|