## 目录

1. [引言:一屏多卡的意义](#1-引言一屏多卡的意义)
2. [displayCount 属性详解](#2-displaycount-属性详解)
   - [2.1 基本概念](#21-基本概念)
   - [2.2 宽度计算公式](#22-宽度计算公式)
   - [2.3 不同数值的视觉效果](#23-不同数值的视觉效果)
3. [itemSpace:卡片间距控制](#3-itemspace卡片间距控制)
4. [实战:8 张电影推荐卡片轮播](#4-实战8-张电影推荐卡片轮播)
5. [代码逐段解析](#5-代码逐段解析)
   - [5.1 数据模型:8 种配色方案](#51-数据模型8-种配色方案)
   - [5.2 CardView:渐变色卡片的叠层构建](#52-cardview渐变色卡片的叠层构建)
   - [5.3 build():displayCount + itemSpace 核心配置](#53-builddisplaycount--itemspace-核心配置)
6. [displayCount 与常规 Swiper 的对比](#6-displaycount-与常规-swiper-的对比)
7. [常见问题与解决方案](#7-常见问题与解决方案)
8. [本系列七篇全览](#8-本系列七篇全览)
9. [总结](#9-总结)

---

## 1. 引言:一屏多卡的意义

在移动应用中,"横向滚动卡片"是一种极为常见的内容展示形式:

| 应用 | 横向滚动场景 | displayCount 典型值 |
|------|------------|-------------------|
| **Apple Music** | "朋友正在听"推荐列表 | 2.5 |
| **Netflix** | 影片横向分类行 | 3.0~3.5 |
| **App Store** | 今日推荐卡片 | 2.0~2.5 |
| **淘宝** | 商品横向推荐 | 2.5~3.0 |
| **Instagram** | 快拍/故事列表 | 3.5~4.0 |

这些场景的共同特征是:**不希望用户一次只看一张卡片**。一屏展示多张卡片有三大好处:

1. **空间效率**:同样的屏幕空间展示更多内容,信息密度更高
2. **视觉暗示**:右侧露出的半张卡片告诉用户"还可以滑"
3. **对比浏览**:用户可以同时看到相邻的几条内容,快速决定是否滑动

HarmonyOS NEXT 的 Swiper 组件通过 `displayCount` 属性完美支持这种"一屏多卡"布局。

---

## 2. displayCount 属性详解

### 2.1 基本概念

`displayCount` 控制 Swiper 一屏(一页)显示多少个子项:

```ets
Swiper() { ... }
.displayCount(2.5)   // 一屏显示 2.5 张卡片
```

**整数部分** —— 完整可见的卡片数量  
**小数部分** —— 右侧露出的下一张卡片的宽度比例

- `displayCount(1)` = 每次显示 1 张(默认,标准轮播)
- `displayCount(2)` = 每次显示 2 张完整卡片
- `displayCount(2.5)` = 每次显示 2 张完整卡片 + 右半张预览
- `displayCount(3)` = 每次显示 3 张完整卡片

### 2.2 宽度计算公式

```text
卡片宽度 = (Swiper宽度 - (displayCount - 1) × itemSpace) / displayCount
```

**实际计算**(假设 Swiper 宽度 = 360vp):

| displayCount | itemSpace | 卡片宽度 | 说明 |
|-------------|-----------|---------|------|
| 1.0 | 0 | 360 | 标准轮播,一屏一张 |
| 2.0 | 12 | 174 | 两卡并排,中间 12px 间距 |
| **2.5** | **12** | **~136.8** | **两卡完整 + 右半张预览** |
| 3.0 | 8 | ~114.7 | 三卡并排 |
| 3.5 | 8 | ~98.3 | 三卡 + 半张预览 |

**以 displayCount=2.5, itemSpace=12 为例**:

```text
卡1 (完整) | 12px | 卡2 (完整) | 12px | 卡3 (半张可见)
<───── 136.8 ─────> <───── 136.8 ─────> <── 68.4 ──>
<────────────────── 总宽 360 ─────────────────────>
```

### 2.3 不同数值的视觉效果

| displayCount | 视觉感受 | 适用场景 |
|-------------|---------|---------|
| **1.0** | 全屏轮播,焦点突出 | 首页大 Banner |
| **1.5** | 右侧露出半张,有"下一页"暗示 | 引导页 |
| **2.0** | 两卡并排,信息均衡 | 对比展示 |
| **2.5** | 两卡 + 半张预览,"继续滑"暗示强烈 | **推荐列表(最常用)** |
| **3.0** | 三卡并排,信息密度高 | 视频分类 / 图标网格 |
| **3.5+** | 密集展示,适合小卡片 | 表情 / Emoji 选择器 |

**2.5 是使用最广泛的配置**。它兼顾了内容可见性(2 张完整卡片)和可发现性(半张预览),是 Apple Music、Netflix 等主流应用的首选值。

---

## 3. itemSpace:卡片间距控制

`itemSpace` 设置相邻卡片之间的间距:

```ets
Swiper() { ... }
.displayCount(2.5)
.itemSpace(12)   // 卡片间距 12vp
```

**itemSpace 的作用**:

| itemSpace | 视觉效果 | 适用场景 |
|-----------|---------|---------|
| 0 | 卡片紧贴,无间隙 | 图片无缝拼接 |
| 8 | 紧凑,略有间隔 | 小卡片网格 |
| **12** | **舒适,明显分隔** | **通用卡片列表** |
| 16 | 宽松,呼吸感强 | 高端品牌展示 |
| 24 | 非常宽松 | 大卡片精选推荐 |

**itemSpace 在 displayCount 计算中的角色**:

itemSpace 不占用卡片的宽度,而是消耗 Swiper 总宽度的一部分。itemSpace 越大,每张卡片的可用宽度越小。

---

## 4. 实战:8 张电影推荐卡片轮播

### 4.1 设计目标

创建一个类似"Netflix 推荐行"的横向滑动卡片列表,一屏显示 2.5 张卡片,8 张不同主题的电影卡片自动轮播。

### 4.2 8 张卡片配色

| # | 电影 | 配色 | 色值 |
|---|------|------|------|
| 1 | 🎬 星际穿越 | 深蓝→蓝 | `#0D47A1` → `#1976D2` |
| 2 | 🎭 悲剧之王 | 深紫→紫 | `#4A148C` → `#7B1FA2` |
| 3 | 🎪 马戏迷城 | 橙→金黄 | `#E65100` → `#FF8F00` |
| 4 | 🎨 梵高之眼 | 深绿→绿 | `#2E7D32` → `#43A047` |
| 5 | 🎵 波西米亚 | 深红→红 | `#B71C1C` → `#E53935` |
| 6 | 🧙 中土传奇 | 深棕→棕 | `#3E2723` → `#6D4C41` |
| 7 | 🤖 机械纪元 | 深灰→灰 | `#37474F` → `#607D8B` |
| 8 | 🌊 深海探秘 | 墨绿→青 | `#004D40` → `#00897B` |

8 种配色覆盖蓝、紫、橙、绿、红、棕、灰、青色系,滑动时色彩变化丰富。

### 4.3 布局预览

```
┌──────────────────────────────────────┐
│  🎪 热门推荐                          │
│  displayCount(2.5) 一屏多卡           │
│                                      │
│  ┌──────┐ ┌──────┐ ┌──── ┐           │
│  │ 🎬   │ │ 🎭   │ │ 🎪  │           │
│  │星际穿│ │悲剧之│ │马戏迷│  ← 半张    │
│  │越    │ │王    │ │城    │  ← 预览    │
│  │科幻· │ │剧情· │ │奇幻· │           │
│  │冒险  │ │励志  │ │家庭  │           │
│  └──────┘ └──────┘ └──── ┘           │
│     ← 12px → ← 12px →                │
│         ○ ● ○ ○ ○ ○ ○ ○              │
│                                      │
│  📐 Swiper + displayCount 多卡片布局    │
│  ● displayCount(2.5)                 │
│  ● itemSpace(12)                     │
│  ● 右半张预览 → 继续滑动               │
│  ● autoPlay 自动轮播                  │
└──────────────────────────────────────┘
```

---

## 5. 代码逐段解析

### 5.1 数据模型:8 种配色方案

```ets
interface CardInfo {
  emoji: string;          // 电影图标
  title: string;          // 标题
  category: string;       // 分类 + 标签
  gradientStart: string;  // 渐变起点色
  gradientEnd: string;    // 渐变终点色
}

private readonly cards: CardInfo[] = [
  { emoji: '🎬', title: '星际穿越', category: '科幻 · 冒险', gradientStart: '#0D47A1', gradientEnd: '#1976D2' },
  { emoji: '🎭', title: '悲剧之王', category: '剧情 · 励志', gradientStart: '#4A148C', gradientEnd: '#7B1FA2' },
  // ... 共 8 张
];
```

**配色设计思路**:

每张卡片使用 `linearGradient` 从深色到亮色的垂直渐变,模拟"电影海报"的视觉质感。8 组配色覆盖了完整的色环,确保滑动时色彩跳跃明显:

```
蓝 → 紫 → 橙 → 绿 → 红 → 棕 → 灰 → 青 → 蓝(循环)
```

### 5.2 CardView:渐变色卡片的叠层构建

```ets
@Builder
private CardView(card: CardInfo) {
  Stack() {
    // 底层:渐变背景
    Column()
      .width('100%').height('100%').borderRadius(16)
      .linearGradient({
        direction: GradientDirection.Bottom,
        colors: [[card.gradientStart, 0], [card.gradientEnd, 1]],
      })

    // 中层:底部半透明遮罩(增强文字可读性)
    Column()
      .width('100%').height(80)
      .linearGradient({
        direction: GradientDirection.Bottom,
        colors: [['#00000000', 0], ['#00000066', 1]],
      })
      .borderRadius({ bottomLeft: 16, bottomRight: 16 })
      .position({ y: 140 })

    // 上层:图标
    Text(card.emoji).fontSize(48).position({ x: 0, y: 20 }).width('100%')

    // 上层:标题 + 分类(底部对齐)
    Column() {
      Text(card.title).fontSize(17).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
      Text(card.category).fontSize(12).fontColor('#FFFFFF').opacity(0.8)
    }
    .position({ x: 14, y: 164 })
  }
  .width('100%').height('100%').clip(true)
}
```

**叠层结构**:

```
┌──────────────────────┐
│        🎬            │  ← 图标 (position y:20)
│                      │
│                      │
│                      │
│  ┌─────────────┐     │  ← 渐变遮罩 (position y:140, height:80)
│  │ 星际穿越     │     │
│  │ 科幻 · 冒险  │     │  ← 文字 (position y:164)
│  └─────────────┘     │
└──────────────────────┘
   ↑ 渐变背景 (铺满)
```

**底部遮罩的作用**:

渐变背景上的白色文字,在浅色背景下可能不易阅读。底部半透明黑色遮罩(从完全透明到 40% 黑色)确保了文字区域始终有足够的对比度,同时不破坏渐变的美感。

### 5.3 build():displayCount + itemSpace 核心配置

```ets
build() {
  Scroll() {
    Column() {
      // 标题
      Text('🎪 热门推荐').fontSize(22).fontWeight(FontWeight.Bold)

      Swiper() {
        ForEach(this.cards, (card: CardInfo, idx: number) => {
          Stack() { this.CardView(card) }
            .width('100%').height('100%')
        }, (card: CardInfo, idx: number): string => idx.toString())
      }
      .width('100%').height(220)
      .index(this.currentIndex)
      .displayCount(2.5)       // ← 核心属性:一屏显示 2.5 张
      .itemSpace(12)            // ← 核心属性:卡片间距 12vp
      .autoPlay(true).interval(3000).loop(true).duration(500)
      .indicator(false)
      .onChange((index) => { this.currentIndex = index; })

      // 指示器 + 说明卡片
      Stack() { this.DotIndicator() }.margin({ top: 4 })
      Stack() { this.FeatureCard() }.width('90%').margin({ top: 14 })
    }
  }
  .backgroundColor('#F2F3F8')
}
```

**displayCount 与卡片子元素的 width 关系**:

注意子元素使用了 `.width('100%').height('100%')`——它们的宽度由 Swiper 根据 displayCount 自动计算分配,不需要也不应该手动设置固定宽度。

**Swiper 高度 220vp 的选择依据**:

相比标准轮播(通常 300vp 以上),多卡片布局的卡片更小,220vp 高度在手机竖屏下刚好显示 2.5 张卡片 + 底部信息,无需用户滚动页面。

---

## 6. displayCount 与常规 Swiper 的对比

| 对比维度 | 常规 Swiper(displayCount=1) | 多卡片布局(displayCount=2.5) |
|---------|-----------------------------|------------------------------|
| **一屏卡片数** | 1 张 | 2.5 张 |
| **信息密度** | 低(一张大图) | 高(多张卡片) |
| **右侧预览** | 无(看不到下一张) | 有(半张预览暗示可滑) |
| **焦点** | 单卡片焦点集中 | 多卡片对比浏览 |
| **滑动步长** | 1 张卡片 | 1 张卡片(还是在同一个分页体系下) |
| **适合场景** | 首页大 Banner | 推荐列表 / 商品展示 |
| **卡片宽度** | 100% Swiper 宽度 | 由 displayCount 公式计算 |

**滑动行为的一致性**:

无论 `displayCount` 设置为多少,Swiper 的滑动步长始终是"一张卡片"。也就是说,从第 0 张滑到第 1 张,内容会移动一个卡片的宽度(不是半个 Swiper 宽度)。

这使得 `displayCount` 更像是一个"显示"属性而非"行为"属性——它只改变"一屏能看到多少",不改变"一次能滑多少"。

---

## 7. 常见问题与解决方案

### 7.1 卡片显示不全

**现象**:displayCount 设置后,卡片右侧被裁剪。

**原因**:卡片子元素设置了固定宽度,覆盖了 Swiper 的自动分配。

**解决**:子元素使用 `.width('100%')`,不要设置固定宽度。

```ets
// ✅ 正确:宽度由 Swiper 自动分配
Stack() { this.CardView(card) }
  .width('100%').height('100%')

// ❌ 错误:固定宽度会覆盖 Swiper 的计算
Stack() { this.CardView(card) }
  .width(150).height(220)
```

### 7.2 displayCount 取整问题

**现象**:设置 `displayCount(2.5)` 但最终显示的不是精确的 2.5 张。

**原因**:Swiper 宽度可能不是 itemSpace 和 displayCount 的整数倍,Swiper 会在内部做适应性调整,最终结果可能与理论值有 ±0.1 的偏差。

**解决方案**:这不是 bug,是正常的舍入行为。只要视觉上接近 2.5 即可。

### 7.3 卡片间距不均匀

**现象**:相邻卡片间距不一致。

**原因**:`itemSpace` 是在 Swiper 侧设置的,但 `padding` 是在卡片侧设置的,两者叠加导致间距翻倍。

**解决**:不要在卡片子元素上设置左右 padding/margin,间距统一由 `itemSpace` 控制。

### 7.4 displayCount 与 loop 配合

**现象**:`displayCount(2.5)` + `loop(true)` 时,循环边界出现空白。

**原因**:loop 模式下的虚拟列表增加了额外的页面副本,displayCount 需要在这些副本之间保持一致的布局。

**解决方案**:大多数情况下可以正常工作。如果出现异常,确认 ForEach 的 keyGenerator 返回了稳定的唯一值。

### 7.5 displayCount 在小屏幕上的表现

**现象**:在屏幕宽度较小的设备上,displayCount 2.5 导致卡片过窄。

**解决方案**:使用条件语句适配不同屏幕:

```ets
// 根据屏幕宽度动态调整
aboutToAppear(): void {
  let w = this.getUIContext()?.getWindowWidth() ?? 360;
  if (w < 360) {
    this.cardCount = 2.0;  // 小屏 2 张
  } else {
    this.cardCount = 2.5;  // 正常 2.5 张
  }
}
```

---

## 8. 本系列七篇全览

本文是"鸿蒙原生 ArkTS 布局实战"系列的第六篇(不含项目回顾篇)。以下是全系列概览:

| # | 标题 | 组件 | 核心属性 | 核心知识点 |
|---|------|------|---------|-----------|
| 1 | Tabs + animateTo 切换动画 | Tabs | `animateTo` | 显式动画编排 |
| 2 | Swiper 轮播图 | Swiper | `autoPlay`, `interval` | 基础轮播配置 |
| 3 | Tabs + vertical 侧边栏 | Tabs | `vertical`, `barPosition.Start` | 左侧导航布局 |
| 4 | Swiper + loop 无限循环 | Swiper | `loop(true)` | 虚拟列表原理 |
| 5 | Swiper + Curve 缓动曲线 | Swiper | `.curve(Curve.xxx)` | 动画节奏控制 |
| **6** | **Swiper + displayCount** | **Swiper** | **`displayCount`, `itemSpace`** | **多卡片并排** |
| — | 项目回顾 | — | — | 24 个编译错误总结 |

**三篇 Swiper 文章的递进关系**:

1. **第 2 篇**(基础轮播):学会 Swiper 的基本使用
2. **第 4 篇**(loop):理解循环原理
3. **第 5 篇**(Curve):掌控动画节奏
4. **第 6 篇(本篇)**(displayCount):掌握多卡片布局

---

## 9. 总结

### 9.1 核心知识点

| 知识点 | 掌握程度 |
|--------|---------|
| displayCount 属性 | ✅ 控制一屏显示的卡片数量 |
| 宽度计算公式 | ✅ `(总宽 - (count-1) × itemSpace) / count` |
| itemSpace 卡片间距 | ✅ 统一间距控制 |
| 2.5 的典型配置 | ✅ 两卡完整 + 半张预览 |
| 卡片子元素 width | ✅ 使用 100%,不设固定值 |
| displayCount vs. 滑动步长 | ✅ 显示 ≠ 行为 |

### 9.2 displayCount 的最佳实践

| 卡片类型 | 推荐 displayCount | 推荐 itemSpace |
|---------|------------------|---------------|
| 大图海报 | 1.5~2.0 | 12~16 |
| 中等卡片 | **2.5** | **12** |
| 小卡片/头像 | 3.5~4.5 | 6~8 |
| Emoji/图标 | 5.0~6.0 | 4~6 |

### 9.3 扩展方向

1. **displayCount 动态切换**:根据屏幕宽度或用户偏好动态调整 count
2. **嵌套 displayCount**:外层 Swiper(全屏轮播)内嵌 displayCount Swiper(分类行)
3. **3D 堆叠效果**:通过自定义动画,让多张卡片在滑动时产生"3D 层叠"效果
4. **drag 手势**:关闭 autoPlay,让用户完全通过拖拽浏览,displayCount 提供预览

### 9.4 推荐阅读

- [HarmonyOS NEXT 开发文档 — Swiper 组件](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-swiper-V5)
- [本系列第 2 篇 — Swiper 轮播图基础](./HarmonyOS_Swiper_Blog.md)
- [本系列第 4 篇 — Swiper + loop 无限循环](./HarmonyOS_Swiper_Loop_Blog.md)

---

> **本文配套完整代码**:`entry/src/main/ets/pages/Index.ets`,209 行,已通过编译验证。  
> **编译命令**:`hvigorw assembleApp --no-daemon`  
> **运行方式**:在 DevEco Studio 中打开项目,连接鸿蒙 NEXT 模拟器或真机运行。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐