网格列表示例
本示例展示如何使用网格布局创建商品卡片列表,支持垂直滚动和水平滚动两种模式。
垂直滚动网格(常见场景)
适用于商店、图库、商品列表等场景。
ts
import { _decorator, Component, instantiate, Prefab, Node, Label, Sprite, Color } from 'cc';
import { VirtualViewList } from 'assets/Script/Core/VirtualList/VirtualViewList';
const { ccclass, property } = _decorator;
interface CardData {
id: number;
title: string;
price: number;
icon: string;
isNew: boolean;
}
@ccclass('VerticalGridExample')
export class VerticalGridExample extends Component {
@property(Prefab)
cardPrefab: Prefab | null = null;
private cardData: CardData[] = [];
onLoad() {
// 生成 300 张卡片数据
this.cardData = Array.from({ length: 300 }, (_, i) => ({
id: i,
title: `商品 ${i + 1}`,
price: Math.floor(Math.random() * 1000) + 10,
icon: `icon_${i % 10}`,
isNew: i < 20 // 前 20 个标记为新品
}));
}
start() {
const list = this.node.getComponent(VirtualViewList)!;
// 预加载提升首屏性能
list.PreloadItems(15);
// 注册模板
list.RegisterTemplate('card', () => instantiate(this.cardPrefab!), true);
// 设置回调
list.SetCallbacks({
onItemInit: (node, index) => this.initCard(node, index),
onItemUpdate: (node, index) => this.updateCard(node, index),
onLoadFinished: () => console.log('网格加载完成')
});
// 加载数据
const types = this.cardData.map(() => 'card');
list.ReloadData(types);
}
private initCard(node: Node, index: number) {
// 绑定点击事件
node.on(Node.EventType.TOUCH_END, () => {
this.onCardClick(index);
});
}
private updateCard(node: Node, index: number) {
const data = this.cardData[index];
// 更新标题
const titleLabel = node.getChildByName('Title')?.getComponent(Label);
if (titleLabel) {
titleLabel.string = data.title;
}
// 更新价格
const priceLabel = node.getChildByName('Price')?.getComponent(Label);
if (priceLabel) {
priceLabel.string = `¥${data.price}`;
priceLabel.color = Color.RED;
}
// 更新新品标签
const newTag = node.getChildByName('NewTag');
if (newTag) {
newTag.active = data.isNew;
}
// 更新图标(这里简化处理)
const icon = node.getChildByName('Icon')?.getComponent(Sprite);
if (icon) {
// TODO: 加载对应图标资源
// resources.load(data.icon, SpriteFrame, (err, frame) => {
// if (!err && node.isValid) icon.spriteFrame = frame;
// });
}
}
private onCardClick(index: number) {
const data = this.cardData[index];
console.log('点击卡片:', data);
// TODO: 打开详情页
}
}水平滚动网格(横向卡片)
适用于推荐列表、横向关卡选择等场景。
ts
@ccclass('HorizontalGridExample')
export class HorizontalGridExample extends Component {
@property(Prefab)
cardPrefab: Prefab | null = null;
start() {
const list = this.node.getComponent(VirtualViewList)!;
// 通过代码配置网格属性(也可在编辑器中设置)
// 注意:这些属性是 protected,仅做演示
// 推荐在编辑器的属性面板中直接配置
list.RegisterTemplate('card', () => instantiate(this.cardPrefab!), true);
list.SetCallbacks({
onItemInit: (node, index) => this.setupCard(node, index),
onItemUpdate: (node, index) => this.setupCard(node, index)
});
const types = Array(100).fill('card');
list.ReloadData(types);
}
private setupCard(node: Node, index: number) {
// 更新卡片内容
const label = node.getComponent(Label);
if (label) {
label.string = `Level ${index + 1}`;
}
}
}场景配置
垂直滚动网格配置(3 列)
在 VirtualViewList 的属性面板中设置:
| 属性 | 值 | 说明 |
|---|---|---|
Layout Type | GRID | 网格布局 |
Scroll Direction | VERTICAL | 垂直滚动 |
Cols | 3 | 3 列 |
Gird Vert Rows Spacing | 10 | 行间距 10px |
Gird Vert Cols Spacing | 10 | 列间距 10px |
Padding Top | 10 | 顶部内边距 |
Padding Bottom | 10 | 底部内边距 |
Cache Ratio | 0.2 | 预加载 20% |
水平滚动网格配置(2 行)
| 属性 | 值 | 说明 |
|---|---|---|
Layout Type | GRID | 网格布局 |
Scroll Direction | HORIZONTAL | 水平滚动 |
Rows | 2 | 2 行 |
Gird Hori Cols Spacing | 20 | 列间距 20px |
Gird Hori Rows Spacing | 10 | 行间距 10px |
Padding Left | 10 | 左侧内边距 |
Padding Right | 10 | 右侧内边距 |
Cache Ratio | 0.2 | 预加载 20% |
卡片预制体设计
垂直网格卡片(商品卡)
节点结构:
CardPrefab (150x200)
├─ Background (Sprite)
├─ Icon (Sprite, 100x100)
├─ NewTag (Node, 可选)
├─ Title (Label)
└─ Price (Label)水平网格卡片(关卡卡)
节点结构:
LevelCard (120x120)
├─ Background (Sprite)
├─ LevelNum (Label)
├─ Stars (Node)
│ ├─ Star1 (Sprite)
│ ├─ Star2 (Sprite)
│ └─ Star3 (Sprite)
└─ Lock (Sprite, 可选)高级用法
动态筛选/排序
ts
// 按价格筛选
filterByPrice(minPrice: number, maxPrice: number) {
const filtered = this.cardData.filter(item =>
item.price >= minPrice && item.price <= maxPrice
);
const types = filtered.map(() => 'card');
list.ReloadData(types);
}
// 排序
sortByPrice(ascending: boolean = true) {
this.cardData.sort((a, b) =>
ascending ? a.price - b.price : b.price - a.price
);
// 增量刷新更高效
list.RefreshData(
this.cardData.map(() => 'card'),
true,
(oldItem, newItem, index) => false // 强制全部更新
);
}添加/移除卡片动画
ts
// 添加新卡片(带动画)
addCard(newCard: CardData, index: number = 0) {
this.cardData.splice(index, 0, newCard);
list.InsertItemAt(index, 'card', true); // true = 播放动画
}
// 移除卡片(带动画)
removeCard(index: number) {
this.cardData.splice(index, 1);
list.RemoveItemAt(index, true); // true = 播放动画
}快速定位到指定卡片
ts
// 滚动到新品区(前 20 个)
scrollToNewItems() {
list.ScrollToIndex(0, 0.5); // 0.5 秒动画
}
// 滚动到指定 ID 的卡片
scrollToCardById(cardId: number) {
const index = this.cardData.findIndex(item => item.id === cardId);
if (index !== -1) {
list.ScrollToIndex(index, 0.5, () => {
console.log('滚动完成');
// 可以添加高亮效果
});
}
}性能优化建议
- 卡片尺寸统一:网格布局中所有卡片应使用相同尺寸,避免额外计算
- 图片懒加载:在
onItemUpdate中异步加载图片资源 - 合理列数:
- 移动端:2-3 列为佳
- 平板:3-4 列
- PC:4-6 列
- 缓存配置:网格布局建议
cacheRatio设为 0.2-0.3
常见布局模式
| 场景 | 滚动方向 | 行/列数 | 适用 |
|---|---|---|---|
| 商城商品 | 垂直 | 2-3 列 | 电商、商店 |
| 图片画廊 | 垂直 | 3-4 列 | 相册、素材库 |
| 关卡选择 | 水平 | 2-3 行 | 游戏关卡 |
| 推荐列表 | 水平 | 1-2 行 | 内容推荐 |
相关示例
- 基础列表示例 - 展示垂直列表的基础用法