509 lines
20 KiB
TypeScript
509 lines
20 KiB
TypeScript
|
||
// Learn TypeScript:
|
||
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
|
||
// Learn Attribute:
|
||
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
|
||
// Learn life-cycle callbacks:
|
||
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
|
||
|
||
import RoundBox from "../../Script/module/Tool/RoundBox";
|
||
import CareerItem from "./CareerItem";
|
||
// import CareerItem from "./CareerItem"
|
||
|
||
const { ccclass, property } = cc._decorator;
|
||
|
||
/**列表排列方式 */
|
||
export enum ListType {
|
||
/**水平排列 */
|
||
Horizontal = 1,
|
||
/**垂直排列 */
|
||
Vertical = 2,
|
||
/**网格排列 */
|
||
Grid = 3
|
||
}
|
||
|
||
/**网格布局中的方向 */
|
||
export enum StartAxisType {
|
||
/**水平排列 */
|
||
Horizontal = 1,
|
||
/**垂直排列 */
|
||
Vertical = 2,
|
||
}
|
||
|
||
/**
|
||
* 列表
|
||
* 根据cocos_example的listView改动而来
|
||
* @author chenkai 2020.7.8
|
||
* @example
|
||
* 1.创建cocos的ScrollView组件,添加List,设置List属性即可
|
||
*
|
||
*/
|
||
@ccclass
|
||
export default class CareerList extends cc.Component {
|
||
|
||
//==================== 属性面板 =========================
|
||
/**列表选项 */
|
||
@property({ type: cc.Node, tooltip: "列表项" })
|
||
public itemRender: cc.Node = null;
|
||
|
||
@property({ type: cc.Node, tooltip: "列表项" })
|
||
public firstRender: cc.Node = null;
|
||
|
||
/**排列方式 */
|
||
@property({ type: cc.Enum(ListType), tooltip: "排列方式" })
|
||
public type: ListType = ListType.Vertical;
|
||
|
||
/**网格布局中的方向 */
|
||
@property({ type: cc.Enum(StartAxisType), tooltip: "网格布局中的方向", visible() { return this.type == ListType.Grid } })
|
||
public startAxis: StartAxisType = StartAxisType.Horizontal;
|
||
|
||
/**列表项之间X间隔 */
|
||
@property({ type: cc.Integer, tooltip: "列表项X间隔", visible() { return (this.type == ListType.Horizontal || this.type == ListType.Grid) } })
|
||
public spaceX: number = 0;
|
||
|
||
/**列表项之间Y间隔 */
|
||
@property({ type: cc.Integer, tooltip: "列表项Y间隔", visible() { return this.type == ListType.Vertical || this.type == ListType.Grid } })
|
||
public spaceY: number = 0;
|
||
|
||
/**上间距 */
|
||
@property({ type: cc.Integer, tooltip: "上间距", visible() { return (this.type == ListType.Vertical || this.type == ListType.Grid) } })
|
||
public padding_top: number = 0;
|
||
|
||
/**下间距 */
|
||
@property({ type: cc.Integer, tooltip: "下间距", visible() { return (this.type == ListType.Vertical || this.type == ListType.Grid) } })
|
||
public padding_buttom: number = 0;
|
||
|
||
/**左间距 */
|
||
@property({ type: cc.Integer, tooltip: "左间距", visible() { return (this.type == ListType.Horizontal || this.type == ListType.Grid || this.type == ListType.Vertical) } })
|
||
public padding_left: number = 0;
|
||
|
||
|
||
|
||
@property(cc.Integer)
|
||
public _padding: number = 0;
|
||
|
||
/**右间距 */
|
||
@property({ type: cc.Integer, tooltip: "右间距", visible() { return (this.type == ListType.Horizontal || this.type == ListType.Grid) } })
|
||
public padding_right: number = 0;
|
||
|
||
//====================== 滚动容器 ===============================
|
||
/**列表滚动容器 */
|
||
public scrollView: cc.ScrollView = null;
|
||
/**scrollView的内容容器 */
|
||
private content: cc.Node = null;
|
||
|
||
//======================== 列表项 ===========================
|
||
/**列表项数据 */
|
||
private itemDataList: Array<any> = [];
|
||
/**应创建的实例数量 */
|
||
private spawnCount: number = 0;
|
||
/**存放列表项实例的数组 */
|
||
private itemList: Array<cc.Node> = [];
|
||
/**item的高度 */
|
||
private itemHeight: number = 0;
|
||
/**item的宽度 */
|
||
private itemWidth: number = 0;
|
||
/**存放不再使用中的列表项 */
|
||
private itemPool: Array<cc.Node> = [];
|
||
|
||
//======================= 计算参数 ==========================
|
||
/**距离scrollView中心点的距离,超过这个距离的item会被重置,一般设置为 scrollVIew.height/2 + item.heigt/2 + space,因为这个距离item正好超出scrollView显示范围 */
|
||
private halfScrollView: number = 0;
|
||
/**上一次content的X值,用于和现在content的X值比较,得出是向左还是向右滚动 */
|
||
private lastContentPosX: number = 0;
|
||
/**上一次content的Y值,用于和现在content的Y值比较,得出是向上还是向下滚动 */
|
||
private lastContentPosY: number = 0;
|
||
/**网格行数 */
|
||
private gridRow: number = 0;
|
||
/**网格列数 */
|
||
private gridCol: number = 0;
|
||
/**刷新时间,单位s */
|
||
private updateTimer: number = 0;
|
||
/**刷新间隔,单位s */
|
||
private updateInterval: number = 0.1;
|
||
/**是否滚动容器 */
|
||
private bScrolling: boolean = false;
|
||
/**刷新的函数 */
|
||
private updateFun: Function = function () { };
|
||
randerChildren: any[];
|
||
topData: any;
|
||
|
||
@property(cc.SpriteAtlas)
|
||
UI: cc.SpriteAtlas = null;
|
||
|
||
onLoad() {
|
||
this.randerChildren = [];
|
||
this.itemHeight = this.itemRender.height;
|
||
this.itemWidth = this.itemRender.width;
|
||
this.scrollView = this.node.getComponent(cc.ScrollView);
|
||
this.content = this.scrollView.content;
|
||
this.content.anchorX = 0;
|
||
this.content.anchorY = 1;
|
||
// this.content.removeAllChildren();
|
||
|
||
// 初始化firstRender
|
||
if (this.firstRender) {
|
||
this.firstRender.parent = this.content;
|
||
// this.firstRender.active = true;
|
||
// this.firstRenderInit();
|
||
// 设置firstRender的位置在顶部
|
||
if (this.type == ListType.Vertical) {
|
||
this.firstRender.setPosition(535, 0);
|
||
} else if (this.type == ListType.Horizontal) {
|
||
this.firstRender.setPosition(this.firstRender.width / 2, 0);
|
||
}
|
||
}
|
||
|
||
this.scrollView.node.on("scrolling", this.onScrolling, this);
|
||
}
|
||
|
||
/**
|
||
* 列表数据 (列表数据复制使用,如果列表数据改变,则需要重新设置一遍数据)
|
||
* @param itemDataList item数据列表
|
||
*/
|
||
public setData(itemDataList: Array<any>, topData: any) {
|
||
// 检查 itemDataList 是否为有效数组,如果不是则初始化为空数组
|
||
this.itemDataList = itemDataList.slice();
|
||
this.topData = topData;
|
||
this.firstRenderInit();
|
||
this.updateContent();
|
||
}
|
||
|
||
/**计算列表的各项参数 */
|
||
private countListParam() {
|
||
let dataLen = this.itemDataList.length;
|
||
let firstRenderHeight = this.firstRender ? this.firstRender.height : 0;
|
||
let firstRenderWidth = this.firstRender ? this.firstRender.width : 0;
|
||
|
||
if (this.type == ListType.Vertical) {
|
||
this.scrollView.horizontal = false;
|
||
this.scrollView.vertical = true;
|
||
this.content.width = this.content.parent.width;
|
||
// 计算高度时考虑firstRender的高度和间距
|
||
this.content.height = firstRenderHeight + (firstRenderHeight > 0 ? this.spaceY : 0) +
|
||
dataLen * this.itemHeight + (dataLen - 3) * this.spaceY + this.padding_top + this.padding_buttom;
|
||
this.spawnCount = Math.round(this.scrollView.node.height / (this.itemHeight + this.spaceY)) + 2; //计算创建的item实例数量,比当前scrollView容器能放下的item数量再加上2个
|
||
this.halfScrollView = this.scrollView.node.height / 2 + this.itemHeight / 2 + this.spaceY; //计算bufferZone,item的显示范围
|
||
this.updateFun = this.updateV;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建列表
|
||
* @param startIndex 起始显示的数据索引 0表示第一项
|
||
* @param offset scrollView偏移量
|
||
*/
|
||
private createList(startIndex: number, offset: cc.Vec2) {
|
||
|
||
//当需要显示的数据长度 > 虚拟列表长度, 删除最末尾几个数据时,列表需要重置位置到scrollView最底端
|
||
if (this.itemDataList.length > this.spawnCount && (startIndex + this.spawnCount - 1) >= this.itemDataList.length) {
|
||
startIndex = this.itemDataList.length - this.spawnCount;
|
||
offset = this.scrollView.getMaxScrollOffset();
|
||
//当需要显示的数据长度 <= 虚拟列表长度, 隐藏多余的虚拟列表项
|
||
} else if (this.itemDataList.length <= this.spawnCount) {
|
||
startIndex = 0;
|
||
}
|
||
|
||
for (let i = 0; i < this.spawnCount; i++) {
|
||
let item: cc.Node;
|
||
//需要显示的数据索引在数据范围内,则item实例显示出来
|
||
if (i + startIndex < this.itemDataList.length) {
|
||
if (this.itemList[i] == null) {
|
||
item = this.getItem();
|
||
this.itemList.push(item);
|
||
item.active = true;
|
||
item.parent = this.content;
|
||
} else {
|
||
item = this.itemList[i];
|
||
}
|
||
//需要显示的数据索引超过了数据范围,则item实例隐藏起来
|
||
} else {
|
||
//item实例数量 > 需要显示的数据量
|
||
if (this.itemList.length > (this.itemDataList.length - startIndex)) {
|
||
item = this.itemList.pop();
|
||
item.removeFromParent();
|
||
this.itemPool.push(item);
|
||
}
|
||
continue;
|
||
}
|
||
|
||
let itemRender: CareerItem = item.getComponent(CareerItem);
|
||
itemRender.itemIndex = i + startIndex;
|
||
itemRender.data = this.itemDataList[i + startIndex];
|
||
itemRender.dataChanged();
|
||
|
||
// 计算firstRender的高度偏移
|
||
let firstRenderHeight = this.firstRender ? this.firstRender.height : 0;
|
||
let firstRenderOffsetY = firstRenderHeight > 0 ? firstRenderHeight + this.spaceY : 0;
|
||
|
||
if (this.type == ListType.Vertical) {
|
||
// item左边对齐padding_left
|
||
item.setPosition(this.padding_left + item.width / 2,
|
||
-item.height * (0.5 + i + startIndex) - this.spaceY * (i + startIndex) - this.padding_top - firstRenderOffsetY);
|
||
}
|
||
}
|
||
|
||
this.scrollView.scrollToOffset(offset);
|
||
}
|
||
|
||
/**获取一个列表项 */
|
||
private getItem() {
|
||
if (this.itemPool.length == 0) {
|
||
return cc.instantiate(this.itemRender);
|
||
} else {
|
||
return this.itemPool.pop();
|
||
}
|
||
}
|
||
|
||
update(dt) {
|
||
if (this.bScrolling == false) {
|
||
return;
|
||
}
|
||
this.updateTimer += dt;
|
||
if (this.updateTimer < this.updateInterval) {
|
||
return;
|
||
}
|
||
this.updateTimer = 0;
|
||
this.bScrolling = false;
|
||
this.updateFun();
|
||
}
|
||
|
||
onScrolling() {
|
||
this.bScrolling = true;
|
||
}
|
||
|
||
/**垂直排列 */
|
||
private updateV() {
|
||
let items = this.itemList;
|
||
let item;
|
||
let bufferZone = this.halfScrollView;
|
||
let isUp = this.scrollView.content.y > this.lastContentPosY;
|
||
let offset = (this.itemHeight + this.spaceY) * items.length;
|
||
|
||
// 计算firstRender的高度偏移
|
||
let firstRenderHeight = this.firstRender ? this.firstRender.height : 0;
|
||
let firstRenderOffsetY = firstRenderHeight > 0 ? firstRenderHeight + this.spaceY : 0;
|
||
|
||
for (let i = 0; i < items.length; i++) {
|
||
item = items[i];
|
||
let viewPos = this.getPositionInView(item);
|
||
if (isUp) {
|
||
if (viewPos) {
|
||
//item上滑时,超出了scrollView上边界,将item移动到下方复用,item移动到下方的位置必须不超过content的下边界
|
||
if (viewPos.y > bufferZone && item.y - offset - this.padding_buttom > -this.content.height - firstRenderOffsetY) {
|
||
let itemRender: CareerItem = item.getComponent(CareerItem);
|
||
let itemIndex = itemRender.itemIndex + items.length;
|
||
itemRender.itemIndex = itemIndex;
|
||
itemRender.data = this.itemDataList[itemIndex];
|
||
itemRender.dataChanged();
|
||
item.y = item.y - offset;
|
||
}
|
||
}
|
||
|
||
} else {
|
||
if (viewPos) {
|
||
//item下滑时,超出了scrollView下边界,将item移动到上方复用,item移动到上方的位置必须不超过content的上边界
|
||
if (viewPos.y < -bufferZone && item.y + offset + this.padding_top < -firstRenderOffsetY) {
|
||
let itemRender: CareerItem = item.getComponent(CareerItem);
|
||
let itemIndex = itemRender.itemIndex - items.length;
|
||
itemRender.itemIndex = itemIndex;
|
||
itemRender.data = this.itemDataList[itemIndex];
|
||
itemRender.dataChanged();
|
||
item.y = item.y + offset;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
this.lastContentPosY = this.scrollView.content.y;
|
||
}
|
||
|
||
|
||
/**获取item在scrollView的局部坐标 */
|
||
private getPositionInView(item) {
|
||
// if (item.parent) {
|
||
let worldPos = item.parent.convertToWorldSpaceAR(item.position);
|
||
let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
|
||
return viewPos;
|
||
// }
|
||
|
||
}
|
||
|
||
/**获取列表数据 */
|
||
public getListData() {
|
||
return this.itemDataList;
|
||
}
|
||
|
||
/**
|
||
* 增加一项数据到列表的末尾
|
||
* @param data 数据
|
||
*/
|
||
public addItem(data: any) {
|
||
this.itemDataList.push(data);
|
||
this.updateContent();
|
||
}
|
||
|
||
/**
|
||
* 增加一项数据到列表指定位置
|
||
* @param index 位置,0表示第1项
|
||
* @param data 数据
|
||
*/
|
||
public addItemAt(index: number, data: any) {
|
||
if (this.itemDataList[index] != null || this.itemDataList.length == index) {
|
||
this.itemDataList.splice(index, 1, data);
|
||
this.updateContent();
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* 删除一项数据
|
||
* @param index 删除项的位置 ,0表示第1项
|
||
*/
|
||
public deleteItem(index: number) {
|
||
if (this.itemDataList[index] != null) {
|
||
this.itemDataList.splice(index, 1);
|
||
this.updateContent();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 改变一项数据
|
||
* @param index 位置,0表示第1项
|
||
* @param data 替换的数据
|
||
*/
|
||
public changeItem(index: number, data: any) {
|
||
if (this.itemDataList[index] != null) {
|
||
this.itemDataList[index] = data;
|
||
this.updateContent();
|
||
}
|
||
}
|
||
|
||
/**获取第一个Item的位置 */
|
||
private updateContent() {
|
||
//显示列表实例为0个
|
||
if (this.itemList.length == 0) {
|
||
this.countListParam();
|
||
this.createList(0, new cc.Vec2(0, 0));
|
||
//显示列表的实例不为0个,则需要重新排列item实例数组
|
||
} else {
|
||
if (this.type == ListType.Vertical) {
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return b.y - a.y;
|
||
});
|
||
} else if (this.type == ListType.Horizontal) {
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return a.x - b.x;
|
||
});
|
||
} else if (this.type == ListType.Grid) {
|
||
if (this.startAxis == StartAxisType.Vertical) {
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return a.x - b.x;
|
||
});
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return b.y - a.y;
|
||
});
|
||
} else if (this.startAxis == StartAxisType.Horizontal) {
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return b.y - a.y;
|
||
});
|
||
this.itemList.sort((a: any, b: any) => {
|
||
return a.x - b.x;
|
||
});
|
||
}
|
||
}
|
||
|
||
this.countListParam();
|
||
|
||
//获取第一个item实例需要显示的数据索引
|
||
var startIndex = this.itemList[0].getComponent(CareerItem).itemIndex;
|
||
|
||
if (this.type == ListType.Grid && this.startAxis == StartAxisType.Vertical) {
|
||
startIndex += (startIndex + this.spawnCount) % this.gridCol;
|
||
} else if (this.type == ListType.Grid && this.startAxis == StartAxisType.Horizontal) {
|
||
startIndex += (startIndex + this.spawnCount) % this.gridRow;
|
||
}
|
||
|
||
//getScrollOffset()和scrollToOffset()的x值是相反的
|
||
var offset: cc.Vec2 = this.scrollView.getScrollOffset();
|
||
offset.x = - offset.x;
|
||
|
||
this.createList(startIndex, offset);
|
||
}
|
||
}
|
||
|
||
public firstRenderInit() {
|
||
// let
|
||
this.firstRender.active = true;
|
||
if (this.randerChildren.length == 0) {
|
||
this.randerChildren = [];
|
||
let length = this.topData.length + 3;
|
||
if (length > this.firstRender.children.length)
|
||
length = this.firstRender.children.length;
|
||
for (let i = 3; i < length; i++) {
|
||
this.randerChildren.push(this.firstRender.children[i]);
|
||
let username = cc.fx.GameTool.subName(this.topData[i - 3].username, 7);
|
||
if (username == "user") username = "匿名玩家";
|
||
this.firstRender.children[i].getChildByName("name").getComponent(cc.Label).string = username + "";
|
||
this.firstRender.children[i].getChildByName("rank").getComponent(cc.Label).string = this.topData[i - 3].addLevel;
|
||
if (this.topData[i - 3].useravatar == "" || this.topData[i - 3].useravatar == null || this.topData[i - 3].useravatar == undefined
|
||
) {
|
||
// this.firstRender.children[i].getChildByName("icon").getComponent(cc.Sprite).spriteFrame = this.defaultsprite;
|
||
}
|
||
else if (this.topData[i - 3].useravatar == "0" || this.topData[i - 3].useravatar == "1" || this.topData[i - 3].useravatar == "2"
|
||
|| this.topData[i - 3].useravatar == "3" || this.topData[i - 3].useravatar == "4" || this.topData[i - 3].useravatar == "5" || this.topData[i - 3].useravatar == "6"
|
||
|| this.topData[i - 3].useravatar == "7" || this.topData[i - 3].useravatar == "8" || this.topData[i - 3].useravatar == "9" ||
|
||
this.topData[i - 3].useravatar == "10") {
|
||
let useravatar = this.topData[i - 3].useravatar;
|
||
let useravatarTemp = "icon_" + useravatar;
|
||
this.firstRender.children[i].getChildByName("mask").getChildByName("icon").getComponent(cc.Sprite).spriteFrame = this.UI.getSpriteFrame(useravatarTemp);
|
||
}
|
||
else this.setPic(this.topData[i - 3].useravatar, this.firstRender.children[i].getChildByName("mask").getChildByName("icon"));
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
public setPic(url, node) {
|
||
// this.node.getChildByName("pic").getChildByName("icon").active = false;
|
||
// this.node.getChildByName("pic").active = false;
|
||
var self = this;
|
||
// let url = this.data.useravatar;
|
||
|
||
cc.assetManager.loadRemote(url, { ext: '.png' }, (err, texture: cc.Texture2D) => {
|
||
if (texture) {
|
||
// node.getChildByName("pic").active = true;
|
||
var sprite = node.getComponent(cc.Sprite);
|
||
sprite.spriteFrame = new cc.SpriteFrame(texture);
|
||
|
||
}
|
||
else {
|
||
|
||
}
|
||
})
|
||
}
|
||
|
||
/**销毁 */
|
||
public onDestroy() {
|
||
//清理列表项
|
||
let len = this.itemList.length;
|
||
for (let i = 0; i < len; i++) {
|
||
if (cc.isValid(this.itemList[i], true)) {
|
||
this.itemList[i].destroy();
|
||
}
|
||
}
|
||
this.itemList.length = 0;
|
||
//清理对象池
|
||
len = this.itemPool.length;
|
||
for (let i = 0; i < len; i++) {
|
||
if (cc.isValid(this.itemPool[i], true)) {
|
||
this.itemPool[i].destroy();
|
||
}
|
||
}
|
||
this.itemPool.length = 0;
|
||
//清理列表数据
|
||
this.itemDataList.length = 0;
|
||
}
|
||
}
|
||
|
||
|