Skip to content

网格列表示例

本示例展示如何使用网格布局创建商品卡片列表,支持垂直滚动和水平滚动两种模式。

垂直滚动网格(常见场景)

适用于商店、图库、商品列表等场景。

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 TypeGRID网格布局
Scroll DirectionVERTICAL垂直滚动
Cols33 列
Gird Vert Rows Spacing10行间距 10px
Gird Vert Cols Spacing10列间距 10px
Padding Top10顶部内边距
Padding Bottom10底部内边距
Cache Ratio0.2预加载 20%

水平滚动网格配置(2 行)

属性说明
Layout TypeGRID网格布局
Scroll DirectionHORIZONTAL水平滚动
Rows22 行
Gird Hori Cols Spacing20列间距 20px
Gird Hori Rows Spacing10行间距 10px
Padding Left10左侧内边距
Padding Right10右侧内边距
Cache Ratio0.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('滚动完成');
      // 可以添加高亮效果
    });
  }
}

性能优化建议

  1. 卡片尺寸统一:网格布局中所有卡片应使用相同尺寸,避免额外计算
  2. 图片懒加载:在 onItemUpdate 中异步加载图片资源
  3. 合理列数
    • 移动端:2-3 列为佳
    • 平板:3-4 列
    • PC:4-6 列
  4. 缓存配置:网格布局建议 cacheRatio 设为 0.2-0.3

常见布局模式

场景滚动方向行/列数适用
商城商品垂直2-3 列电商、商店
图片画廊垂直3-4 列相册、素材库
关卡选择水平2-3 行游戏关卡
推荐列表水平1-2 行内容推荐

相关示例