# 鸿蒙 ArkTS List + edgeEffect 无边缘效果列表布局深度解析

---

## 第一章 鸿蒙 List 组件总览

### 1.1 列表在移动端的重要性

列表是移动端应用中最基础、最高频的 UI 模式。从微信的会话列表、淘宝的商品流、抖音的评论面板到设置页的选项清单,几乎所有的信息呈现都离不开列表。在鸿蒙 ArkUI 框架中,`List` 是专门为高效滚动列表场景而设计的一等公民组件。

### 1.2 List 的核心能力矩阵

| 能力 | 说明 |

|------|------|

| 虚拟滚动 | 只渲染可见区域附近的列表项,大幅降低内存和 GPU 开销 |

| 多种布局方向 | 支持纵向(默认)和横向排列 |

| 边缘效果控制 | 通过 `edgeEffect` 属性精细控制滑动到边界的视觉反馈 |

| 编辑模式 | 支持 `editMode` + `onMove` 实现拖拽排序 |

| 粘性标题 | `sticky` 属性实现分组吸顶效果 |

| 下拉刷新 | 配合 `Swiper` 或 `Refresh` 组件实现 |

| 分隔线 | `divider` 属性灵活配置分割线样式 |

| 链式动画 | `chainAnimation` 开启 iOS 风格阻尼效果 |

### 1.3 List 与 Scroll 的本质区别

许多初学者将 `List` 和 `Scroll` 混为一谈,二者的核心差异在于**虚拟滚动**:

- **Scroll**:全量渲染所有子元素,适合子元素数量少(通常 < 20)且结构固定的场景。

- **List**:仅渲染可视区域内的 `ListItem`(加上预缓存 `cachedCount` 项),滚动时动态回收和复用不可见的节点。适合数据量大、动态增删的场景。

**通俗类比**:Scroll 是"把整卷报纸铺开",List 是"只打开当前版面的窗户"——后者在长列表场景下的内存优势是数量级的。

---

## 第二章 edgeEffect 属性深度剖析

### 2.1 edgeEffect 是什么

`edgeEffect` 是 List 组件(以及 Grid、Scroll 等可滚动容器)上用于控制"滑动到内容边界时的视觉效果"的属性。它决定了当用户的滚动操作超出内容区域时,容器如何反馈给用户。

在 ArkTS 中,edgeEffect 接受以下枚举值:

| 枚举值 | 效果 | 适用场景 |

|--------|------|---------|

| `EdgeEffect.None` | 无任何效果,滑动到边界立即停止 | 底部 TabBar 页面、地图浮层面板 |

| `EdgeEffect.Spring` | 弹性回弹 + 边缘发光(默认) | 普通列表页、聊天记录 |

| `EdgeEffect.Fade` | 边缘渐隐消失 | 图片画廊、卡片横向滑动 |

### 2.2 EdgeEffect.None —— 本文核心

```ets

List()

  .edgeEffect(EdgeEffect.None)

```

当设置 `EdgeEffect.None` 时,列表滑动到顶部或底部时会**立即停止**,不产生任何回弹动画或发光效果。用户感知是"硬边界"——滚动戛然而止。

**内部行为:**

1. 触摸事件的位置变化被映射为列表的偏移量。

2. 当计算出的偏移量超出内容边界(offset < 0 或 offset > maxOffset)时,直接钳制到边界值,不再生成超出范围的偏移。

3. 手指抬起时,没有回弹动画,列表静止在边界位置。

### 2.3 EdgeEffect.Spring —— 默认弹性回弹

```ets

List()

  .edgeEffect(EdgeEffect.Spring)

```

这是 List 的默认行为。当用户快速上滑超过底部内容时,列表会产生以下效果:

1. **超出偏移**:列表偏移量允许短暂超出内容边界(类似拉橡皮筋)。

2. **回弹动画**:手指抬起后,列表以弹性曲线动画回到边界位置。

3. **边缘发光**:在超出边界的区域出现蓝色半透明发光带(发光颜色可通过 `edgeColor` 自定义)。

### 2.4 EdgeEffect.Fade —— 边缘渐变

```ets

List()

  .edgeEffect(EdgeEffect.Fade)

```

此模式下,滑动到边缘时,边界处的列表项逐渐变为透明,产生"沉入黑暗"的效果。常见于:

- 图片浏览器的左右滑动翻页。

- 卡片横向列表的边缘提示。

- 杂志风格的阅读应用。

### 2.5 三种效果的视觉对比

| 维度 | None | Spring | Fade |

|------|------|--------|------|

| 超出边界 | ❌ 不允许 | ✅ 允许短暂超出 | ❌ 不允许 |

| 回弹动画 | ❌ 无 | ✅ 有弹性回弹 | ❌ 无 |

| 发光效果 | ❌ 无 | ✅ 蓝色发光 | ❌ 无 |

| 透明度变化 | ❌ 不变 | ❌ 不变 | ✅ 边缘渐隐 |

| 用户感知 | 硬边界 | 弹性边界 | 渐变消失 |

### 2.6 edgeEffect 在开发文档中的表述

需要注意的是,HarmonyOS 不同历史版本的 API 文档中,`EdgeEffect` 枚举的成员名称可能略有差异:

- **API 10 及之前**:`EdgeEffect.None` / `EdgeEffect.Spring` / `EdgeEffect.Fade`

- **API 11+**:新增 `EdgeEffect.Auto`(等同于 Spring,作为更语义化的别名)

- **API 12+**:推荐使用 `EdgeEffect.None` 和 `EdgeEffect.Spring`,二者语义最清晰

因此在实际开发中,建议始终使用 `EdgeEffect.None` 和 `EdgeEffect.Spring`,以获得最佳的跨版本兼容性。

---

## 第三章 完整代码逐段深度解析

### 3.1 导入与类型定义

```ets

import { promptAction } from '@kit.ArkUI';

```

仅导入了 `promptAction` 用于 Toast 提示。`List`、`ListItem`、`Column`、`Row`、`Text` 等基础组件由框架隐式导入。

两个数据接口的设计:

```ets

interface ListItemData {

  id: number;

  title: string;

  desc: string;

  color: string;

  icon: string;

}

interface TabItem {

  icon: string;

  label: string;

  active: boolean;

}

```

- `ListItemData`:列表项的数据模型,包含标题、描述、颜色、图标等字段。

- `TabItem`:底部选项卡的数据模型,用于模拟实际项目中的 TabBar。

### 3.2 状态变量设计

```ets

@State listData: ListItemData[] = [];

@State currentEffect: EdgeEffect = EdgeEffect.None;

```

两个 `@State` 变量分别管理:

- **数据层**:列表项的完整数据集。

- **表现层**:当前的边缘效果模式。

这种"数据与表现分离"的设计,使得通过一个按钮切换 `currentEffect` 即可让整个列表的滚动行为发生变化,充分体现了声明式编程的威力。

### 3.3 数据的初始化与循环色盘

```ets

initData(): void {

  const items: ListItemData[] = [];

  for (let i = 1; i <= this.itemCount; i++) {

    items.push({

      id: i,

      title: '列表项 ' + i,

      desc: '...' + (this.currentEffect === EdgeEffect.None ? '无任何回弹效果' : '有弹性回弹效果') + '。',

      color: this.getColorByIndex(i),

      icon: this.getIconByIndex(i),

    });

  }

  this.listData = items;

}

```

设计要点:

1. **动态描述文本**:每个列表项的描述中嵌入当前边缘效果的说明,使用户在滚动阅读时能自然理解当前模式。

2. **循环色盘**:`getColorByIndex` 从 8 种颜色中循环选取,确保相邻项颜色不同,视觉上易于区分边界。

3. **图标分配**:Emoji 图标通过模运算循环分配,增加视觉丰富度。

### 3.4 toggleEdgeEffect 的交互逻辑

```ets

toggleEdgeEffect(): void {

  if (this.currentEffect === EdgeEffect.None) {

    this.currentEffect = EdgeEffect.Spring;

  } else {

    this.currentEffect = EdgeEffect.None;

  }

  this.initData();

  promptAction.showToast({ ... });

}

```

此方法的精妙之处在于:

- **状态取反**:使用条件判断而非硬编码赋值,确保可无限切换。

- **数据重建**:`initData()` 重新生成包含当前效果描述的列表数据。

- **即时反馈**:Toast 告知用户当前模式,避免用户疑惑。

- **副作用最小化**:只修改 `@State` 变量,框架自动推导 UI 更新。

### 3.5 说明卡片 —— 教育性 UI 的设计

```ets

Column({ space: 4 }) {

  Text('当前效果:EdgeEffect.' + (this.currentEffect === EdgeEffect.None ? 'None(无效果)' : 'Spring(弹性回弹)'))

  Text(this.currentEffect === EdgeEffect.None

    ? '列表滑动到顶部或底部时「无任何效果」...'

    : '列表滑动到顶部或底部时有「弹性回弹 + 蓝色发光」效果...')

}

```

这个说明卡片承担了"文档即代码"的教育角色:

1. **标题行**:明确显示当前枚举值。

2. **描述行**:解释当前模式的视觉效果和典型场景。

3. **布局位置**:固定在列表上方,用户每次切换后都能立即看到说明。

### 3.6 List 容器的完整属性配置

```ets

List({ space: 8, initialIndex: 0 }) {

  ForEach(this.listData, (item: ListItemData) => {

    ListItem() {

      Row({ space: 12 }) {

        // 图标 + 文字 + 色块

      }

    }

    .borderRadius(10)

    .backgroundColor(Color.White)

  }, (item: ListItemData): string => item.id.toString())

}

.edgeEffect(this.currentEffect)  // ★ 核心

.divider({ strokeWidth: '0.5vp', color: '#E8E8E8', startMargin: 60, endMargin: 16 })

.scrollBar(BarState.Off)

.layoutWeight(1)

```

配置要点逐项解析:

**1. List 构造参数**

```ets

List({ space: 8, initialIndex: 0 })

```

- `space: 8`:列表项之间的间距为 8vp。

- `initialIndex: 0`:列表初始滚动到第 0 项(顶部)。

**2. edgeEffect**

```ets

.edgeEffect(this.currentEffect)

```

核心属性,绑定 `@State` 变量,切换时自动生效。

**3. divider**

```ets

.divider({ strokeWidth: '0.5vp', color: '#E8E8E8', startMargin: 60, endMargin: 16 })

```

- 0.5vp 的细线,颜色极浅(#E8E8E8)。

- 左缩进 60vp(避开图标区域),右缩进 16vp,视觉上更干净。

- 分隔线仅在列表项之间绘制,不会绘制在列表顶部和底部。

**4. scrollBar**

```ets

.scrollBar(BarState.Off)

```

隐藏滚动条,让界面更简洁。如果需要指示内容的长度,可以改为 `BarState.Auto`(滚动时显示)。

**5. layoutWeight**

```ets

.layoutWeight(1)

```

让 List 填充 Column 中的剩余空间,这是适应不同屏幕高度的关键。

### 3.7 ListItem 卡片的视觉设计

```ets

ListItem() {

  Row({ space: 12 }) {

    Text(item.icon)           // 左侧 Emoji 图标

    Column({ space: 4 }) {    // 中间文字

      Text(item.title)

      Text(item.desc)

    }

    .layoutWeight(1)

    Row({ space: 4 }) {       // 右侧色块 + 箭头

      Column().backgroundColor(item.color)

      Text('\u203A')

    }

  }

}

.borderRadius(10)

.backgroundColor(Color.White)

```

设计细节:

1. **左图标右箭头的标准列表样式**:这是 iOS UITableViewCell 的经典样式,用户已经习惯。

2. **layoutWeight(1)**:确保文字区域填满中间空间,右侧色块和箭头始终固定在右边。

3. **色块**:12×12vp 的小圆点,颜色与数据源中的 `color` 字段对应,作为视觉标签。

4. **白色圆角卡片**:`.borderRadius(10)` 配合白色背景,形成常见的列表卡片风格。

### 3.8 底部模拟 TabBar

```ets

Row() {

  ForEach(this.tabList, (tab: TabItem) => {

    Column({ space: 2 }) {

      Text(tab.icon).fontSize(20)

      Text(tab.label).fontSize(10)

    }

    .layoutWeight(1)

  })

}

.height(56)

.backgroundColor(Color.White)

.shadow({ radius: 8, color: 'rgba(0,0,0,0.08)', offsetY: -2 })

```

底部 TabBar 的作用不仅仅是装饰:

1. **场景演示**:展示 `edgeEffect(None)` 在实际项目中的典型使用场景——列表底部紧跟 TabBar。

2. **视觉锚点**:使用 `EdgeEffect.Spring` 时,列表回弹会与 TabBar 产生视觉竞争,而 `None` 模式则让 TabBar 始终稳固在底部。

3. **上阴影**:`.shadow` 在 TabBar 顶部投射阴影,形成"列表在上、TabBar 在下"的层次感。

---

## 第四章 边缘效果的底层实现原理

### 4.1 滚动系统的坐标系

理解 edgeEffect 需要先理解鸿蒙的滚动坐标系:

```

┌─────────────────────┐   ← 屏幕顶部(y = 0)

│  可见视口           │

│  (Viewport)         │

│                     │

│  ┌───────────────┐  │

│  │ 内容区         │  │

│  │ (Content)      │  │

│  │ 高度 > 视口高度│  │

│  └───────────────┘  │

│                     │

├─────────────────────┤   ← 屏幕底部(y = viewportHeight)

│  TabBar             │

└─────────────────────┘

```

滚动偏移量 `offset` 从 0 开始(内容顶部对齐视口顶部),最大值为 `contentHeight - viewportHeight`(内容底部对齐视口底部)。

### 4.2 三种 edgeEffect 的偏移量处理

**None 模式:**

```

offset ∈ [0, maxOffset]

超出边界时 → 直接钳制到边界值

手指抬起 → 无回弹

```

**Spring 模式:**

```

offset ∈ [-overScroll, maxOffset + overScroll]

超出边界 → 允许短暂超出(overScroll ≈ 视口高度的 20%)

手指抬起 → spring 曲线动画回到边界

同时绘制边缘发光效果

```

**Fade 模式:**

```

offset ∈ [0, maxOffset]

正常范围内滚动,超出边界时内容透明度渐变

手指抬起 → 无回弹

```

### 4.3 边缘发光的实现

Spring 模式下的蓝色发光效果是通过一个覆盖层(overlay)实现的。当偏移量超出边界时,框架在超出区域绘制一个渐变半透明的蓝色蒙层:

- 顶部发光:线性渐变从 (0,0) 到 (0, gradientHeight),颜色从半透明蓝到完全透明。

- 底部发光:线性渐变从 (0, viewportHeight - gradientHeight) 到 (0, viewportHeight),颜色从透明到半透明蓝。

这个发光的颜色可以通过 Color 类型的 `edgeColor` 属性自定义:

```ets

List()

  .edgeEffect(EdgeEffect.Spring)

  .edgeColor('#FF3B30')  // 将发光色改为红色

```

---

## 第五章 List 的核心滚动机制

### 5.1 虚拟滚动原理

List 的虚拟滚动是它区别于 Scroll 的最核心特性。其工作原理如下:

1. **测量阶段**:计算每个 ListItem 的高度(如果有固定高度则直接使用,否则测量一次后缓存)。

2. **可见窗口**:根据当前滚动偏移量,计算"哪些列表项在视口内"。

3. **按需渲染**:只渲染可见项 + 上下各 `cachedCount` 个预缓存项。

4. **回收复用**:当列表项滚出缓存区时,销毁或回收到节点池。

5. **位置占位**:不可见的列表项用等高的空白占位符替代,保持滚动条尺寸准确。

### 5.2 cachedCount 的优化配置

```ets

List()

  .cachedCount(3)  // 视口上下各预缓存 3 项

```

- 默认值通常为 1~2。

- 对于 ListItem 内容复杂(包含图片、动画)的场景,建议增大到 3~5,提升滑动流畅度。

- 对于极长列表(1000+ 项),cachedCount 不宜过大,否则内存占用会线性增长。

### 5.3 chainAnimation —— 链式动画

```ets

List()

  .chainAnimation(true)

```

开启后,列表的滚动带有类似于 iOS UIScrollView 的**减速阻尼**效果:

- 快速滑动时,列表项以略带阻尼的方式跟随手指。

- 惯性滑动时,速度逐渐衰减,衰减曲线不是线性的,而是先快后慢。

- 视觉上更加自然,符合物理世界的运动规律。

此属性与 `edgeEffect` 独立配置,可以组合使用:

- `None + chainAnimation(true)`:硬边界但滚动过程有阻尼,适合工具类应用。

- `Spring + chainAnimation(false)`:有回弹但滚动衰减均匀,适合信息流应用。

---

## 第六章 性能优化与最佳实践

### 6.1 列表项复用与 key 生成

```ets

ForEach(this.listData, (item: ListItemData) => {

  // 渲染

}, (item: ListItemData): string => item.id.toString())

```

ForEach 的第三个参数是 key 生成器,对于 List 来说尤为重要:

- **使用业务 ID 作为 key**:确保框架在数据变化时能精确追踪每个列表项。

- **避免使用索引作为 key**:索引在列表项增删时会变化,导致框架错误复用节点。

### 6.2 ListItem 的高度稳定性

List 的性能与 ListItem 的高度是否稳定密切相关:

- **固定高度**:所有 ListItem 高度一致,框架可以极快地定位任意位置的项。

- **动态高度**:每个 ListItem 高度不同,框架需要在滚动过程中持续测量。

如果 ListItem 的高度是动态的(如不同长度的文字),建议:

1. 尽量使用 `maxLines` 限制文字行数。

2. 使用固定比例的图片(如 `aspectRatio`)。

3. 避免在 ListItem 内部使用 `LayoutWeight` 或 `FlexGrow`。

### 6.3 减少列表项中的层级嵌套

```ets

// ✅ 推荐:减少嵌套层级

ListItem() {

  Row() {

    Image().width(40).height(40)

    Column() {

      Text()

      Text()

    }.layoutWeight(1)

  }

}

// ❌ 不推荐:过多嵌套

ListItem() {

  Column() {

    Row() {

      Column() {

        Row() {

          // ...

        }

      }

    }

  }

}

```

过多的嵌套会增加布局计算的复杂度,在快速滚动时可能导致帧率下降。

### 6.4 图片懒加载

如果 ListItem 中包含图片,务必使用懒加载:

```ets

Image(item.imageUrl)

  .objectFit(ImageFit.Cover)

  .loadStatus((status: ImageLoadStatus) => {

    if (status === ImageLoadStatus.LOADING) {

      // 显示占位图

    }

  })

```

`Image` 组件本身具备异步加载能力,不会阻塞 UI 线程。

---

## 第七章 常见问题与故障排查

### 7.1 滑动卡顿不流畅

**原因排查**:

1. ListItem 内容过于复杂(多层嵌套、大图加载)。

2. `cachedCount` 太小,快速滚动时来不及渲染。

3. 数据更新频繁导致 ForEach 频繁重建。

**解决方案**:

1. 简化 ListItem 的层级结构。

2. 增大 `cachedCount`(推荐 3~5)。

3. 使用不可变数据(Immutable Data)减少不必要的 diff。

4. 开启 `chainAnimation(true)` 缓解卡顿感知。

### 7.2 edgeEffect 设置后无效

**原因排查**:

1. `edgeEffect` 设置在了 ListItem 上而非 List 上。

2. List 的内容高度不足以产生滚动(内容未超出视口)。

3. 父容器拦截了触摸事件。

**解决方案**:

1. 确认 `.edgeEffect()` 调用在 List 上,而非 ListItem。

2. 增加列表项数量或减小列表高度,使其可滚动。

3. 检查父容器的 `hitTestBehavior` 设置。

### 7.3 分隔线没有显示

**原因排查**:

1. `divider` 属性配置在 ListItem 上而非 List 上。

2. ListItem 之间有间距(`space`),分隔线只绘制在相邻项之间,不覆盖间距区域。

**解决方案**:

1. 确认 `divider` 是 List 的属性。

2. 如果间距过大导致分隔线不可见,考虑减小 `space` 或在 ListItem 内部手动绘制底部边框。

### 7.4 列表滑不到底

**原因排查**:

1. List 的高度设置了固定值,且小于内容高度。

2. 父容器限制了 List 的可用空间。

**解决方案**:

1. 确保 List 使用 `.layoutWeight(1)` 或 `.height('100%')` 弹性撑满。

2. 检查父容器的高度链:从根节点到 List 的每一层都需要正确传递高度。

### 7.5 底部 TabBar 被列表回弹遮挡

**原因排查**:

使用 `EdgeEffect.Spring` 时,列表回弹效果会出现在 TabBar 之上,造成视觉干扰。

**解决方案**:

1. 使用 `EdgeEffect.None` 彻底关闭回弹。

2. 或者在 List 底部增加 padding,让回弹区在视觉上不侵入 TabBar 区域。

---

## 第八章 嵌套滚动与冲突处理

### 8.1 嵌套滚动的典型场景

在实际项目中,List 常常嵌套在其他可滚动容器中:

```

Column {

  Scroll {           // ← 外层滚动

    Column {

      Header 区域

      List {         // ← 内层滚动

        ListItem × N

      }

    }

  }

  TabBar             // ← 固定底部

}

```

这种结构的问题是:手指在 List 区域滑动时,内层 List 和外层 Scroll 都可能响应,造成滚动冲突。

### 8.2 nestedScroll 属性

ArkUI 提供了 `nestedScroll` 属性来解决嵌套滚动冲突:

```ets

List()

  .nestedScroll({

    scrollForward: NestedScrollMode.SELF_FIRST,    // 前向滚动

    scrollBackward: NestedScrollMode.SELF_FIRST,   // 后向滚动

  })

```

`NestedScrollMode` 的取值:

| 取值 | 行为 |

|------|------|

| `SELF_FIRST` | 自己先消费滚动,自己无法再滚动时交给父容器 |

| `PARENT_FIRST` | 父容器先消费,父容器不消费时自己再消费 |

| `SELF_ONLY` | 自己消费,不传递给父容器 |

| `PARENT_ONLY` | 全部交给父容器,自己不消费 |

### 8.3 edgeEffect 与嵌套滚动的关联

在嵌套滚动场景下,`edgeEffect` 的行为会受到 `nestedScroll` 的影响:

- 当 `nestedScroll` 设置为 `SELF_FIRST` 时,内层 List 的 `edgeEffect.Spring` 回弹效果会在自己边界生效,不会传递到外层。

- 当设置为 `PARENT_FIRST` 时,内层 List 可能不会产生独立的回弹效果,而是将超出滚动量传递给父容器。

建议在嵌套滚动场景中使用 `edgeEffect.None`,避免多层回弹叠加造成混乱的视觉体验。

---

## 第九章 多设备适配策略

### 9.1 不同屏幕尺寸的列表适配

鸿蒙设备覆盖手机、平板、折叠屏、车机等。List 的适配要点:

**1. 列表项宽度自适应**

```ets

ListItem() {

  Row() {

    // 内容

  }

  .width('100%')   // 撑满 List

}

```

**2. 动态列表项高度**

不同设备的字体大小不同,列表项的高度不应固定,而应由内容撑开或使用 `layoutWeight` 弹性分配。

**3. 列表项数量自适应**

```ets

aboutToAppear(): void {

  const displayInfo = display.getDefaultDisplaySync();

  const height = displayInfo.height;

  // 根据屏幕高度决定加载更多的预缓存数据

  this.cachedCount = height > 1000 ? 5 : 3;

}

```

### 9.2 横竖屏切换

横竖屏切换时,List 的宽度和高度都会变化。ArkUI 的响应式布局会自动处理重新测量,但需要注意:

1. ListItem 内的文字换行规则:横屏时宽度更大,文字占用行数可能减少。

2. 列表项的高度适应:使用 `maxLines` 固定最大行数,避免横竖屏切换时列表项高度变化导致的视觉跳跃。

### 9.3 折叠屏适配

折叠屏在折叠与展开状态之间切换时,屏幕尺寸会变化。可以通过监听折叠状态重新配置 List:

```ets

aboutToAppear(): void {

  display.on('foldStatusChange', (status: display.FoldStatus) => {

    if (status === display.FoldStatus.FOLDED) {

      // 折叠状态:单列布局

      this.listConfig.columnCount = 1;

    } else {

      // 展开状态:可以考虑使用 Grid 替代 List

      this.listConfig.columnCount = 2;

    }

  });

}

```

---

## 第十章 List 与 Grid / Column 的选型对比

### 10.1 List vs Grid

| 维度 | List | Grid |

|------|------|------|

| 维度 | 一维(单列/单行) | 二维(多列/多行) |

| 虚拟滚动 | ✅ 支持 | ❌ 不支持 |

| 数据量级 | 无限(虚拟滚动) | 建议 ≤ 100 项 |

| 边缘效果 | ✅ edgeEffect | ✅ edgeEffect |

| 编辑模式 | ✅ onMove | ✅ onDrag |

| 典型场景 | 聊天记录、设置列表 | 首页宫格、照片墙 |

**选择建议**:

- 超过 50 项的列表 → **List**

- 需要多列布局 → **Grid**

- 动态增删的长列表 → **List**

- 需要拖拽排序的宫格 → **Grid**(二维拖拽体验更好)

### 10.2 List vs Column + Scroll

| 维度 | List | Column + Scroll |

|------|------|-----------------|

| 虚拟滚动 | ✅ 是 | ❌ 否 |

| 渲染策略 | 按需渲染 | 全量渲染 |

| 性能 | 高(适合长列表) | 低(适合短列表) |

| 动画 | 内置滚动动画 | 需自定义 |

| 开发复杂度 | 低(API 丰富) | 中(需手动管理) |

**选择建议**:

- 列表项 > 20 → **List**

- 列表项 ≤ 20 且结构简单 → **Column + Scroll** 或直接 **Column**

- 需要优化极致流畅度 → **List**

### 10.3 List vs WaterFlow

`WaterFlow` 是鸿蒙提供的瀑布流布局组件,适用于图片社区、商品推荐等场景:

| 维度 | List | WaterFlow |

|------|------|-----------|

| 布局 | 单列 | 多列不等高 |

| 虚拟滚动 | ✅ | ✅ |

| 边缘效果 | ✅ edgeEffect | ✅ edgeEffect |

| 列数控制 | 1 列 | 多列(自适应) |

| 典型场景 | 列表信息流 | 图片瀑布流 |

---

## 第十一章 生产环境实战经验

### 11.1 列表项点击事件的处理

在 List 中,有两种方式处理点击事件:

**方式一:在 ListItem 上绑定**

```ets

ListItem()

  .onClick(() => {

    // 处理点击

  })

```

优点:每个 ListItem 独立处理,适合差异化的点击逻辑。

**方式二:在 List 上使用 onItemClick**

```ets

List()

  .onItemClick((item: ListItemData, index: number) => {

    // 统一处理

  })

```

优点:代码集中,性能更优(只需注册一个事件监听)。

**建议**:如果所有列表项的点击逻辑相同/相似,使用 `onItemClick`;如果需要执行不同的操作(如不同行跳转不同页面),在 ListItem 上分别绑定。

### 11.2 滚动到指定位置

```ets

// 滚动到指定索引

this.scroller.scrollToIndex(10, true, ScrollAlign.START)

// 平滑滚动到指定偏移

this.scroller.scrollTo({ yOffset: 500, duration: 300 })

// 滚动到顶部

this.scroller.scrollEdge(Edge.Top)

```

需要先创建 `Scroller` 实例:

```ets

private scroller: Scroller = new Scroller();

List({ scroller: this.scroller }) { ... }

```

### 11.3 下拉刷新集成

List 与 `Refresh` 组件配合实现下拉刷新:

```ets

Refresh({ refreshing: this.isRefreshing, onRefresh: () => this.loadData() }) {

  List() { ... }

}

```

注意:如果列表的 `edgeEffect` 设置为 `None`,下拉刷新时的"超出"行为需要由 `Refresh` 组件接管,List 自身不再产生回弹,两者不会冲突。

### 11.4 列表数据的增量加载(分页)

```ets

List()

  .onReachStart(() => {

    // 滚动到顶部,加载更早的数据

  })

  .onReachEnd(() => {

    // 滚动到底部,加载更多数据

  })

  .onScrollIndex((start: number, end: number) => {

    // 监控当前可见范围

    if (end >= this.listData.length - 5) {

      this.loadMoreData();  // 反向截流加载更多

    }

  })

```

推荐使用 `onScrollIndex` + 反向截流的方式实现"无限滚动",相比于 `onReachEnd`,这种方式可以提前触发加载,减少用户等待感。

---

## 第十二章 无障碍访问

### 12.1 为列表项添加无障碍语义

```ets

ListItem()

  .accessibilityText('列表项 1,这是一个示例列表项')

  .accessibilityDescription('滑动可查看更多内容')

  .accessibilityLevel('yes')

```

### 12.2 边缘效果的无障碍考量

对于视障用户,`edgeEffect` 的视觉反馈(发光、回弹)无法通过屏幕阅读器感知。因此:

1. 如果使用 `EdgeEffect.None`,建议在列表末尾添加提示性文本(如"已到底部"),而不是只依赖视觉上的戛然而止。

2. 可以使用 `accessibilityText` 在列表末尾添加隐藏的提示项。

3. 通过 `announceForAccessibility` API 在滚动到边界时主动播报。

---

## 第十三章 测试策略

### 13.1 单元测试

List 的边缘效果切换逻辑应当通过单元测试验证:

```ets

function testToggleEdgeEffect(): void {

  const page = new ListEdgeEffectPage();

  assert(page.currentEffect === EdgeEffect.None);

  page.toggleEdgeEffect();

  assert(page.currentEffect === EdgeEffect.Spring);

  page.toggleEdgeEffect();

  assert(page.currentEffect === EdgeEffect.None);

}

function testEdgeEffectDataBinding(): void {

  const page = new ListEdgeEffectPage();

  page.currentEffect = EdgeEffect.Spring;

  page.initData();

  // 验证描述文本包含"弹性回弹"字样

  assert(page.listData[0].desc.indexOf('弹性回弹') >= 0);

  page.currentEffect = EdgeEffect.None;

  page.initData();

  assert(page.listData[0].desc.indexOf('无任何') >= 0);

}

```

### 13.2 UI 自动化测试

使用 `UiTestKit` 模拟列表的滑动操作:

```ets

import { UiDriver, ON, Swipe, Drag } from '@kit.UiTestKit';

async function testScrollToBottom(): Promise<void> {

  const driver = await UiDriver.create();

  // 从底部向上快速滑动

  await Swipe.create(driver)

    .start(200, 600)

    .end(200, 100)

    .speed(2000)

    .perform();

  // 验证边缘效果(无回弹时,列表立即停止在底部)

  // 此处可通过截图对比或坐标验证

}

```

### 13.3 edgeEffect 的视觉验证

边缘效果的视觉验证自动化难度较高,建议采用以下策略:

1. **截图对比**:在 Spring 和 None 模式下分别截图,通过像素对比确认发光区域的有无。

2. **滚动偏移验证**:在 Spring 模式下,快速滑动到底部后等待 100ms,检查列表的滚动偏移量是否被钳制在 maxOffset。

3. **手动测试 Checklist**:

   - [ ] None 模式:快速上滑,列表到底部无回弹

   - [ ] None 模式:快速下滑,列表到顶部无发光

   - [ ] Spring 模式:快速上滑,列表到底部有弹性回弹 + 蓝色发光

   - [ ] Spring 模式:缓慢拖动超出边界,手指不抬起,观察发光区域

   - [ ] 切换按钮:来回切换 5 次,验证每次都生效

---

## 第十四章 与业界方案的对比

### 14.1 对比 Android RecyclerView + EdgeEffect

| 维度 | 鸿蒙 List + edgeEffect | Android RecyclerView + EdgeEffect |

|------|-----------------------|----------------------------------|

| API 风格 | 声明式属性 `.edgeEffect()` | Android 13+ `edgeEffectFactory` |

| 发光颜色 | `edgeColor` 自定义 | `EdgeEffect.setColor()` |

| None 模式 | `.edgeEffect(EdgeEffect.None)` | 需自定义 `EdgeEffectFactory` 返回空实现 |

| Spring 模式 | 内置(默认) | 内置(默认) |

| Fade 模式 | 内置 | 需自定义 |

| 代码量 | 1 行 | 约 20 行 |

鸿蒙方案的核心优势在于**声明式 API 的统一性**——所有滚动容器的边缘效果都通过同一个 `edgeEffect` 属性控制,学习成本极低。

### 14.2 对比 iOS UICollectionView + bounces

| 维度 | 鸿蒙 List + edgeEffect | iOS UICollectionView |

|------|-----------------------|---------------------|

| 无效果 | `.edgeEffect(.None)` | `bounces = false` |

| 弹性回弹 | `.edgeEffect(.Spring)` | `bounces = true`(默认) |

| 发光效果 | 内置蓝色发光 | 无内置发光(需自定义) |

| 边缘渐变 | `.edgeEffect(.Fade)` | 无等效 API |

| 自定义颜色 | `.edgeColor()` | 无原生支持 |

iOS 的 `bounces` 只有开/关两个选项,不提供发光和渐变效果。鸿蒙的 `edgeEffect` 在表达能力上更丰富。

### 14.3 对比 Flutter ListView + physics

| 维度 | 鸿蒙 List | Flutter ListView |

|------|-----------|------------------|

| 无效果 | `EdgeEffect.None` | `NeverScrollableScrollPhysics` |

| 弹性回弹 | `EdgeEffect.Spring` | `BouncingScrollPhysics` |

| 发光效果 | 内置 | 无内置 |

| 链式动画 | `chainAnimation` | `ClampingScrollPhysics` |

| 自定义物理 | 有限(3 种取值) | 丰富(可继承 ScrollPhysics) |

Flutter 在自定义滚动物理行为方面最灵活(通过继承 `ScrollPhysics`),但鸿蒙的声明式 API 在易用性上更胜一筹。

### 14.4 总结:鸿蒙方案的优势定位

| 优势 | 说明 |

|------|------|

| 声明式 API | 一行代码设置边缘效果,与其他 UI 属性风格一致 |

| 三种效果可选 | None / Spring / Fade,覆盖绝大多数场景 |

| 发光颜色可自定义 | 通过 `edgeColor` 灵活适配品牌色 |

| 统一性 | List、Grid、Scroll 使用相同的 edgeEffect 属性 |

| 无侵入 | 边缘效果的开关不影响列表的滚动逻辑和数据绑定 |

---

## 第十五章 典型行业应用场景

### 15.1 即时通讯 —— 聊天记录列表

聊天记录的列表是最典型的 List 应用场景。edgeEffect 的选择取决于交互设计:

- **Spring 模式**:滑动到底部有回弹,给用户"已经看完历史消息"的反馈。

- **None 模式**:如果聊天页面底部有输入框,使用 None 避免回弹干扰输入框的位置。

### 15.2 电商 —— 商品分类页

左侧一级分类(List)+ 右侧二级分类(Grid)是电商 App 的经典布局。左侧列表使用 None 模式更加合适——因为右侧内容区的滚动不应被左侧列表的回弹所干扰。

### 15.3 地图 —— 底部浮层面板

地图类的底部浮层面板(如高德地图的底部详情卡片)通常使用 `EdgeEffect.None`:

- 浮层面板从底部滑出,紧贴屏幕底部。

- 如果使用 Spring 模式,向下滑动时回弹效果会与地图的平移手势产生冲突。

- None 模式确保用户只能看到浮层面板的内容,不会"拉出"底部的空白区域。

### 15.4 内容社区 —— 信息流

抖音、小红书等内容社区的信息流推荐使用 `EdgeEffect.Spring`:

- 回弹效果给用户"内容丰富、可以一直刷下去"的心理暗示。

- 蓝色发光在视觉上标记了"边界",告知用户已经到达当前列表的末端。

- 配合下拉刷新(Refresh 组件)实现"下拉刷新、上拉加载更多"的完整交互。

---

## 第十六章 进阶技巧

### 16.1 自定义边缘发光颜色

通过 `edgeColor` 属性可以改变发光的颜色,适配不同品牌风格:

```ets

List()

  .edgeEffect(EdgeEffect.Spring)

  .edgeColor('#FF9500')   // 橙色发光

```

### 16.2 动态切换 edgeEffect

结合滚动位置,可以在滚动到不同区域时动态切换边缘效果:

```ets

onScrollIndex((start: number) => {

  if (start === 0) {

    // 在顶部时使用 Spring,方便下拉刷新

    this.currentEffect = EdgeEffect.Spring;

  } else {

    // 不在顶部时使用 None,避免误触回弹

    this.currentEffect = EdgeEffect.None;

  }

})

```

### 16.3 结合 Divider 实现更丰富的分割线

```ets

.divider({

  strokeWidth: '1vp',

  color: '#E8E8E8',

  startMargin: 72,   // 避开左侧图标

  endMargin: 16,

})

```

通过调整 `startMargin` 和 `endMargin`,可以实现类似 iOS 的"缩进分割线"效果——第一个列表项顶部不显示分割线,其余项的分割线从文字区域开始而非从屏幕最左侧开始。

### 16.4 列表项入场动画

结合 `TransitionEffect` 为列表项添加入场动画:

```ets

ListItem()

  .transition(

    TransitionEffect.translate({ y: 20 })

      .combine(TransitionEffect.opacity(0))

      .animation({ duration: 300, curve: Curve.EaseOut })

  )

```

每个列表项在首次出现时从下方 20vp 的位置淡入,形成流畅的入场序列。

---

## 第十七章 总结与展望

### 17.1 核心要点回顾

本文通过一个完整的 List + edgeEffect 对比示例,深度剖析了以下核心知识点:

1. **List 组件**:鸿蒙原生的高性能虚拟滚动列表,支持方向、分隔线、缓存、链式动画等特性。

2. **edgeEffect 属性**:控制列表滑动到边界时的视觉反馈,有 None(无效果)、Spring(弹性回弹 + 发光)、Fade(边缘渐变)三种模式。

3. **EdgeEffect.None 的应用场景**:底部 TabBar 页面、地图浮层面板、嵌套滚动容器——凡是回弹效果会干扰其他 UI 元素的场景都应使用 None。

4. **边缘效果的底层原理**:None 模式下偏移量被钳制在边界值,Spring 模式下允许短暂超出再弹性恢复,Fade 模式下边界内容透明度渐变。

5. **状态驱动切换**:通过 @State + edgeEffect 绑定,一键切换整个列表的滚动行为。

### 17.2 架构设计启示

edgeEffect 的设计体现了 ArkUI 框架"声明式优先"的设计哲学:

- **关注点分离**:列表的数据内容与滚动行为通过 `edgeEffect` 属性解耦,互不影响。

- **渐进增强**:从默认的 Spring(通用友好)到 None(特定优化),开发者可以根据场景逐步打磨细节。

- **统一抽象**:List、Grid、Scroll 共用同一套 edgeEffect 枚举,降低学习成本。

### 17.3 未来演进方向

展望未来,鸿蒙 List 组件可能在以下方向持续演进:

1. **更丰富的 ScrollPhysics**:允许开发者注入自定义的滚动物理行为,超越当前的三种固定模式。

2. **嵌套滚动的标准化**:嵌套滚动冲突的解决方案将更加自动化,减少 `nestedScroll` 的手动配置。

3. **列表项动画更多样**:入场动画、删除动画、拖拽动画的 API 将更加完善。

4. **性能进一步提升**:通过 GPU 加速渲染和更高效的节点复用算法,支持万级列表的丝滑滚动。

5. **跨设备滚动同步**:手机和平板的列表滚动位置可以实时同步。

### 17.4 写给读者的话

边缘效果是移动端开发中"细节见真章"的一个典型例子。一个小小的发光、一段短暂的回弹,或是刻意为之的"无效果",都传达着设计者的思考。鸿蒙 ArkTS 通过 `edgeEffect` 这一行代码,给了开发者选择的权利——你可以选择 Spring 的活力,也可以选择 None 的克制。

本文的示例代码已在 HarmonyOS NEXT 环境下编译通过并运行验证。读者可以在 DevEco Studio 中打开项目,运行 ListEdgeEffect 页面,用手指体验 None 与 Spring 的真实差异。

**最好的设计,往往来自对细节的反复打磨。**

---

## 附录 A:完整代码索引

```ets

// 文件:entry/src/main/ets/pages/ListEdgeEffect.ets(348 行)

// 路由配置:entry/src/main/resources/base/profile/main_pages.json

// 导航入口:entry/src/main/ets/pages/Index.ets(功能按钮「边缘效果」)

```

## 附录 B:关键 API 参考

| API | 类型 | 说明 |

|-----|------|------|

| `List({ space, initialIndex })` | 构造 | 创建列表,space 为项间距 |

| `List().edgeEffect(effect)` | 属性 | 设置边缘效果(核心) |

| `List().edgeColor(color)` | 属性 | 自定义边缘发光颜色 |

| `List().divider(config)` | 属性 | 设置分隔线 |

| `List().scrollBar(barState)` | 属性 | 设置滚动条显示策略 |

| `List().cachedCount(count)` | 属性 | 设置预缓存项数 |

| `List().chainAnimation(enabled)` | 属性 | 开启链式阻尼动画 |

| `List().nestedScroll(config)` | 属性 | 配置嵌套滚动行为 |

| `List().onScrollIndex(callback)` | 事件 | 滚动位置变化回调 |

| `List().onReachEnd(callback)` | 事件 | 滚动到底部回调 |

| `List().onReachStart(callback)` | 事件 | 滚动到顶部回调 |

| `ListItem().borderRadius(r)` | 属性 | 列表项圆角 |

| `EdgeEffect.None` | 枚举 | 无边缘效果 |

| `EdgeEffect.Spring` | 枚举 | 弹性回弹 + 发光 |

| `EdgeEffect.Fade` | 枚举 | 边缘渐隐 |

---

*本文所附示例代码已在 HarmonyOS NEXT(API 12+)环境下编译通过并运行验证。文中观点仅代表作者基于当前版本(DevEco Studio 6.1 / ArkUI 3.x / ArkTS 3.x)的技术分析,后续版本 API 如有变动请以官方文档为准。*

Logo

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

更多推荐