如何在CocosCreator中做一個List
CocosCreator版本:2.3.4
cocos沒有List組件,所以要自己寫。從cocos的example項目中找到assets/case/02_ui/05_listView的demo來改造。
自寫一個虛擬列表,有垂直布局,水平布局,網格布局和Padding的List
Demo地址:https://files-cdn.cnblogs.com/files/gamedaybyday/cocos2.3.4_ListViewDemo_Grid.7z
cocos原來的LayOut做列表,有100個數(shù)據(jù)就有100個實例(左側圖)。
而虛擬列表則只有你看見的實例存在,當滑動時會循環(huán)使用。(右側圖)
List使用方法
使用方法就是在ScrollView上添加List組件即可。
List的item列表項直接放在content下,并賦值給List組件,item需要添加繼承自ItemRender的對象,用于數(shù)據(jù)刷新。
代碼中給List設置數(shù)據(jù)
//設置排行榜數(shù)據(jù) let rankData = []; for(let i=0;i<100;i++){ rankData.push({rank:i, name:"名稱"}); } this.rankList.setData(rankData);
源碼
// 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 ItemRender from "./ItemRender" const { ccclass, property } = cc._decorator; /**列表排列方式 */ export enum ListType { /**水平排列 */ Horizontal = 1, /**垂直排列 */ Vertical = 2, /**網格排列 */ Grid = 3 } /**網格布局中的方向 */ export enum StartAxisType { /**水平排列 */ Horizontal = 1, /**垂直排列 */ Vertical = 2, } /** * 列表 * 根據(jù)cocos_example的listView改動而來 * @author chenkai 2020.7.8 * @example * 1.創(chuàng)建cocos的ScrollView組件,添加List,設置List屬性即可 * */ @ccclass export default class List extends cc.Component { //==================== 屬性面板 ========================= /**列表選項 */ @property({ type: cc.Node, tooltip: "列表項" }) public itemRender: 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) } }) 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; //======================== 列表項 =========================== /**列表項數(shù)據(jù) */ private itemDataList: Array<any> = []; /**應創(chuàng)建的實例數(shù)量 */ private spawnCount: number = 0; /**存放列表項實例的數(shù)組 */ private itemList: Array<cc.Node> = []; /**item的高度 */ private itemHeight: number = 0; /**item的寬度 */ private itemWidth: number = 0; /**存放不再使用中的列表項 */ private itemPool: Array<cc.Node> = []; //======================= 計算參數(shù) ========================== /**距離scrollView中心點的距離,超過這個距離的item會被重置,一般設置為 scrollVIew.height/2 + item.heigt/2 + space,因為這個距離item正好超出scrollView顯示范圍 */ private halfScrollView: number = 0; /**上一次content的X值,用于和現(xiàn)在content的X值比較,得出是向左還是向右滾動 */ private lastContentPosX: number = 0; /**上一次content的Y值,用于和現(xiàn)在content的Y值比較,得出是向上還是向下滾動 */ private lastContentPosY: number = 0; /**網格行數(shù) */ private gridRow: number = 0; /**網格列數(shù) */ private gridCol: number = 0; /**刷新時間,單位s */ private updateTimer: number = 0; /**刷新間隔,單位s */ private updateInterval: number = 0.1; /**是否滾動容器 */ private bScrolling: boolean = false; /**刷新的函數(shù) */ private updateFun: Function = function () { }; onLoad() { 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(); this.scrollView.node.on("scrolling", this.onScrolling, this); } /** * 列表數(shù)據(jù) (列表數(shù)據(jù)復制使用,如果列表數(shù)據(jù)改變,則需要重新設置一遍數(shù)據(jù)) * @param itemDataList item數(shù)據(jù)列表 */ public setData(itemDataList: Array<any>) { this.itemDataList = itemDataList.slice(); this.updateContent(); } /**計算列表的各項參數(shù) */ private countListParam() { let dataLen = this.itemDataList.length; if (this.type == ListType.Vertical) { this.scrollView.horizontal = false; this.scrollView.vertical = true; this.content.width = this.content.parent.width; this.content.height = dataLen * this.itemHeight + (dataLen - 1) * this.spaceY + this.padding_top + this.padding_buttom; this.spawnCount = Math.round(this.scrollView.node.height / (this.itemHeight + this.spaceY)) + 2; //計算創(chuàng)建的item實例數(shù)量,比當前scrollView容器能放下的item數(shù)量再加上2個 this.halfScrollView = this.scrollView.node.height / 2 + this.itemHeight / 2 + this.spaceY; //計算bufferZone,item的顯示范圍 this.updateFun = this.updateV; } else if (this.type == ListType.Horizontal) { this.scrollView.horizontal = true; this.scrollView.vertical = false; this.content.width = dataLen * this.itemWidth + (dataLen - 1) * this.spaceX + this.padding_left + this.padding_right; this.content.height = this.content.parent.height; this.spawnCount = Math.round(this.scrollView.node.width / (this.itemWidth + this.spaceX)) + 2; this.halfScrollView = this.scrollView.node.width / 2 + this.itemWidth / 2 + this.spaceX; this.updateFun = this.udpateH; } else if (this.type == ListType.Grid) { if (this.startAxis == StartAxisType.Vertical) { this.scrollView.horizontal = false; this.scrollView.vertical = true; this.content.width = this.content.parent.width; //如果left和right間隔過大,導致放不下一個item,則left和right都設置為0,相當于不生效 if (this.padding_left + this.padding_right + this.itemWidth + this.spaceX > this.content.width) { this.padding_left = 0; this.padding_right = 0; console.error("padding_left或padding_right過大"); } this.gridCol = Math.floor((this.content.width - this.padding_left - this.padding_right) / (this.itemWidth + this.spaceX)); this.gridRow = Math.ceil(dataLen / this.gridCol); this.content.height = this.gridRow * this.itemHeight + (this.gridRow - 1) * this.spaceY + this.padding_top + this.padding_buttom; this.spawnCount = Math.round(this.scrollView.node.height / (this.itemHeight + this.spaceY)) * this.gridCol + this.gridCol * 2; this.halfScrollView = this.scrollView.node.height / 2 + this.itemHeight / 2 + this.spaceY; this.updateFun = this.updateGrid_V; } else if (this.startAxis == StartAxisType.Horizontal) { this.scrollView.horizontal = true; this.scrollView.vertical = false; //計算高間隔 this.content.height = this.content.parent.height; //如果left和right間隔過大,導致放不下一個item,則left和right都設置為0,相當于不生效 if (this.padding_top + this.padding_buttom + this.itemHeight + this.spaceY > this.content.height) { this.padding_top = 0; this.padding_buttom = 0; console.error("padding_top或padding_buttom過大"); } this.gridRow = Math.floor((this.content.height - this.padding_top - this.padding_buttom) / (this.itemHeight + this.spaceY)); this.gridCol = Math.ceil(dataLen / this.gridRow); this.content.width = this.gridCol * this.itemWidth + (this.gridCol - 1) * this.spaceX + this.padding_left + this.padding_right; this.spawnCount = Math.round(this.scrollView.node.width / (this.itemWidth + this.spaceX)) * this.gridRow + this.gridRow * 2; this.halfScrollView = this.scrollView.node.width / 2 + this.itemWidth / 2 + this.spaceX; this.updateFun = this.updateGrid_H; } } } /** * 創(chuàng)建列表 * @param startIndex 起始顯示的數(shù)據(jù)索引 0表示第一項 * @param offset scrollView偏移量 */ private createList(startIndex: number, offset: cc.Vec2) { //當需要顯示的數(shù)據(jù)長度 > 虛擬列表長度, 刪除最末尾幾個數(shù)據(jù)時,列表需要重置位置到scrollView最底端 if (this.itemDataList.length > this.spawnCount && (startIndex + this.spawnCount - 1) >= this.itemDataList.length) { startIndex = this.itemDataList.length - this.spawnCount; offset = this.scrollView.getMaxScrollOffset(); //當需要顯示的數(shù)據(jù)長度 <= 虛擬列表長度, 隱藏多余的虛擬列表項 } else if (this.itemDataList.length <= this.spawnCount) { startIndex = 0; } for (let i = 0; i < this.spawnCount; i++) { let item: cc.Node; //需要顯示的數(shù)據(jù)索引在數(shù)據(jù)范圍內,則item實例顯示出來 if (i + startIndex < this.itemDataList.length) { if (this.itemList[i] == null) { item = this.getItem(); this.itemList.push(item); item.parent = this.content; } else { item = this.itemList[i]; } //需要顯示的數(shù)據(jù)索引超過了數(shù)據(jù)范圍,則item實例隱藏起來 } else { //item實例數(shù)量 > 需要顯示的數(shù)據(jù)量 if (this.itemList.length > (this.itemDataList.length - startIndex)) { item = this.itemList.pop(); item.removeFromParent(); this.itemPool.push(item); } continue; } let itemRender: ItemRender = item.getComponent(ItemRender); itemRender.itemIndex = i + startIndex; itemRender.data = this.itemDataList[i + startIndex]; itemRender.dataChanged(); if (this.type == ListType.Vertical) { //因為content的錨點X是0,所以item的x值是content.with/2表示居中,錨點Y是1,所以item的y值從content頂部向下是0到負無窮。所以item.y= -item.height/2時,是在content的頂部。 item.setPosition(this.content.width / 2, -item.height * (0.5 + i + startIndex) - this.spaceY * (i + startIndex) - this.padding_top); } else if (this.type == ListType.Horizontal) { item.setPosition(item.width * (0.5 + i + startIndex) + this.spaceX * (i + startIndex) + this.padding_left, -this.content.height / 2); } else if (this.type == ListType.Grid) { if (this.startAxis == StartAxisType.Vertical) { var row = Math.floor((i + startIndex) / this.gridCol); var col = (i + startIndex) % this.gridCol; item.setPosition(item.width * (0.5 + col) + this.spaceX * col + this.padding_left, -item.height * (0.5 + row) - this.spaceY * row - this.padding_top); item.opacity = 255; } else if (this.startAxis == StartAxisType.Horizontal) { var row = (i + startIndex) % this.gridRow; var col = Math.floor((i + startIndex) / this.gridRow); item.setPosition(item.width * (0.5 + col) + this.spaceX * col + this.padding_left, -item.height * (0.5 + row) - this.spaceY * row - this.padding_top); item.opacity = 255; } } } 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; for (let i = 0; i < items.length; i++) { item = items[i]; let viewPos = this.getPositionInView(item); if (isUp) { //item上滑時,超出了scrollView上邊界,將item移動到下方復用,item移動到下方的位置必須不超過content的下邊界 if (viewPos.y > bufferZone && item.y - offset - this.padding_buttom > -this.content.height) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex + items.length; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.y = item.y - offset; } } else { //item下滑時,超出了scrollView下邊界,將item移動到上方復用,item移動到上方的位置必須不超過content的上邊界 if (viewPos.y < -bufferZone && item.y + offset + this.padding_top < 0) { let itemRender: ItemRender = item.getComponent(ItemRender); 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; } /**水平排列 */ private udpateH() { let items = this.itemList; let item; let bufferZone = this.halfScrollView; let isRight = this.scrollView.content.x > this.lastContentPosX; let offset = (this.itemWidth + this.spaceX) * items.length; for (let i = 0; i < items.length; i++) { item = items[i]; let viewPos = this.getPositionInView(item); if (isRight) { //item右滑時,超出了scrollView右邊界,將item移動到左方復用,item移動到左方的位置必須不超過content的左邊界 if (viewPos.x > bufferZone && item.x - offset - this.padding_left > 0) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex - items.length; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.x = item.x - offset; } } else { //item左滑時,超出了scrollView左邊界,將item移動到右方復用,item移動到右方的位置必須不超過content的右邊界 if (viewPos.x < -bufferZone && item.x + offset + this.padding_right < this.content.width) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex + items.length; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.x = item.x + offset; } } } this.lastContentPosX = this.scrollView.content.x; } /**網格垂直排列 */ private updateGrid_V() { let items = this.itemList; let item: cc.Node; let bufferZone = this.halfScrollView; let isUp = this.scrollView.content.y > this.lastContentPosY; let offset = (this.itemHeight + this.spaceY) * (this.spawnCount / this.gridCol); for (let i = 0; i < items.length; i++) { item = items[i]; let viewPos = this.getPositionInView(item); if (isUp) { //item上滑時,超出了scrollView上邊界,將item移動到下方復用,item移動到下方的位置必須不超過content的下邊界 if (viewPos.y > bufferZone && item.y - offset - this.padding_buttom > -this.content.height) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex + (this.spawnCount / this.gridCol) * this.gridCol; if (this.itemDataList[itemIndex] != null) { item.y = item.y - offset; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.opacity = 255; } else { item.y = item.y - offset; itemRender.itemIndex = itemIndex; item.opacity = 0; } } } else {//item下滑時,超出了scrollView下邊界,將item移動到上方復用,item移動到上方的位置必須不超過content的上邊界 if (viewPos.y < -bufferZone && item.y + offset + this.padding_top < 0) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex - (this.spawnCount / this.gridCol) * this.gridCol; if (this.itemDataList[itemIndex] != null) { item.y = item.y + offset; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.opacity = 255; } else { item.y = item.y + offset; itemRender.itemIndex = itemIndex; item.opacity = 0; } } } } this.lastContentPosY = this.scrollView.content.y; } /**網格水平排列 */ private updateGrid_H() { let items = this.itemList; let item; let bufferZone = this.halfScrollView; let isRight = this.scrollView.content.x > this.lastContentPosX; let offset = (this.itemWidth + this.spaceX) * (this.spawnCount / this.gridRow); for (let i = 0; i < items.length; i++) { item = items[i]; let viewPos = this.getPositionInView(item); if (isRight) { //item右滑時,超出了scrollView右邊界,將item移動到左方復用,item移動到左方的位置必須不超過content的左邊界 if (viewPos.x > bufferZone && item.x - offset - this.padding_left > 0) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex - (this.spawnCount / this.gridRow) * this.gridRow; if (this.itemDataList[itemIndex] != null) { item.x = item.x - offset; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.opacity = 255; } else { item.x = item.x - offset; itemRender.itemIndex = itemIndex; item.opacity = 0; } } } else { //item左滑時,超出了scrollView左邊界,將item移動到右方復用,item移動到右方的位置必須不超過content的右邊界 if (viewPos.x < -bufferZone && item.x + offset + this.padding_right < this.content.width) { let itemRender: ItemRender = item.getComponent(ItemRender); let itemIndex = itemRender.itemIndex + (this.spawnCount / this.gridRow) * this.gridRow; if (this.itemDataList[itemIndex] != null) { item.x = item.x + offset; itemRender.itemIndex = itemIndex; itemRender.data = this.itemDataList[itemIndex]; itemRender.dataChanged(); item.opacity = 255; } else { item.x = item.x + offset; itemRender.itemIndex = itemIndex; item.opacity = 0; } } } } this.lastContentPosX = this.scrollView.content.x; } /**獲取item在scrollView的局部坐標 */ private getPositionInView(item) { let worldPos = item.parent.convertToWorldSpaceAR(item.position); let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos); return viewPos; } /**獲取列表數(shù)據(jù) */ public getListData() { return this.itemDataList; } /** * 增加一項數(shù)據(jù)到列表的末尾 * @param data 數(shù)據(jù) */ public addItem(data: any) { this.itemDataList.push(data); this.updateContent(); } /** * 增加一項數(shù)據(jù)到列表指定位置 * @param index 位置,0表示第1項 * @param data 數(shù)據(jù) */ public addItemAt(index: number, data: any) { if (this.itemDataList[index] != null || this.itemDataList.length == index) { this.itemDataList.splice(index, 1, data); this.updateContent(); } } /** * 刪除一項數(shù)據(jù) * @param index 刪除項的位置 ,0表示第1項 */ public deleteItem(index: number) { if (this.itemDataList[index] != null) { this.itemDataList.splice(index, 1); this.updateContent(); } } /** * 改變一項數(shù)據(jù) * @param index 位置,0表示第1項 * @param data 替換的數(shù)據(jù) */ 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實例數(shù)組 } 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實例需要顯示的數(shù)據(jù)索引 var startIndex = this.itemList[0].getComponent(ItemRender).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 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; //清理列表數(shù)據(jù) this.itemDataList.length = 0; } }
以上就是如何在CocosCreator中做一個List的詳細內容,更多關于CocosCreator List的資料請關注腳本之家其它相關文章!