鸿蒙原生 ArkTS 布局方式之 Scroll 可滚动容器入门:何时需要 Scroll


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、前言

在鸿蒙应用开发中,内容溢出是最常见的布局问题之一。当页面内容总尺寸超过容器可视区域时,开发者必须决定:超出部分怎么办?

鸿蒙 ArkTS 给出了优雅的答案——Scroll 可滚动容器组件

本文通过一个完整的可运行示例,带你深入理解 Scroll 容器的使用场景、核心属性和最佳实践。无论你是初学 HarmonyOS 还是从其他平台迁移,本文都将帮你建立对 Scroll 的系统认知。


二、为什么需要 Scroll 容器?

2.1 内容溢出的本质

假设页面有一个高度固定的 Column,内部循环渲染了 20 个卡片,每个高 52vp + 间距 6vp,总高度约 1154 vp。而容器的固定高度只有 300vp

在这 300vp 里只能放下约 5 个卡片,剩下的 15 个被裁剪了——用户完全看不到它们。

这就是内容溢出问题最直观的表现:不是因为数据丢失,而是因为布局容器没有提供滚动能力

2.2 没有 Scroll 的后果

在鸿蒙 ArkTS 中:

  • ColumnRow 不会自动滚动
  • Flex 容器 不会自动滚动
  • 超出父容器尺寸的内容默认被 clip 裁剪

这意味着:一旦内容超出屏幕高度,就会被"吃掉"。

2.3 Scroll 容器的解决方案

Scroll 是最基础的可滚动容器。它的核心能力:

  • 包裹子组件(通常是一个 Column 或 Row)
  • 子组件内容超出自身尺寸时,自动启用滚动交互
  • 用户通过手指滑动(或鼠标滚轮)查看全部内容

一句话总结:当内容可能超出容器时,在外面包一层 Scroll


三、Scroll 容器核心概念(API 24)

3.1 基本用法

Scroll() {
  Column() {
    Text('内容1');
    Text('内容2');
    // 更多内容...
  }
  .width('100%')
}
.width('100%')
.height(300)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)

3.2 核心属性详解

(1)scrollable —— 设置滚动方向
枚举值说明
ScrollDirection.Vertical纵向滚动(最常用)
ScrollDirection.Horizontal横向滚动
ScrollDirection.None禁止滚动
(2)scrollBar —— 控制滚动条
枚举值说明
BarState.Auto滚动时显示,不滚动时隐藏
BarState.On始终显示
BarState.Off始终隐藏
(3)edgeEffect —— 边缘效果
枚举值说明
EdgeEffect.Spring弹性回弹(推荐)
EdgeEffect.Fade边缘渐隐
EdgeEffect.None无效果

3.3 事件监听

Scroll() { /* ... */ }
.onScroll((x, y) => { /* 滚动偏移量回调 */ })
.onScrollEnd(() => { /* 滚动结束 */ })
.onReachStart(() => { /* 到达顶部 */ })
.onReachEnd(() => { /* 到达底部 */ })

3.4 程序化滚动控制

private scroller: Scroller = new Scroller();

Scroll(this.scroller) { /* ... */ }

// 滚动控制方法
this.scroller.scrollTo({ xOffset: 0, yOffset: 200 });
this.scroller.scrollEdge(Edge.Top);
this.scroller.scrollEdge(Edge.Bottom);
this.scroller.scrollPage({ next: true });

四、示例应用详解

4.1 整体架构

示例采用 Tab 切换对比 设计:同一个位置展示"无 Scroll"和"有 Scroll"两种状态,通过点击 Tab 切换,直观对比效果差异。

┌─ 标题 ──────────────────────────────┐
│  Scroll 可滚动容器入门               │
├─ 说明区域 ───────────────────────────┤
│  【布局要点】Scroll 的基本概念……      │
├─ Tab 切换 ───────────────────────────┤
│  [❌无Scroll]  [✅有Scroll]           │
├─ 演示区域 ───────────────────────────┤
│  ⚠️/✅ 状态提示条                    │
│  ① Scroll 基础用法    >              │
│  ② 内容溢出问题      >              │
│  ……更多卡片……        >              │
└──────────────────────────────────────┘

4.2 关键技术设计

Tab 切换逻辑
@State selectedTab: number = 0;

Stack() {
  if (this.selectedTab === 0) {
    this.NonScrollView();
  } else {
    this.ScrollView();
  }
}

点击 Tab 时更新 selectedTabStack 保持两个视图位置一致,切换无缝。

无 Scroll 版本
Flex() {
  Column({ space: 6 }) {
    ForEach(this.GenerateItems(), (item: CardItem) => {
      this.CardItemView(item)
    }, (item: CardItem) => item.id.toString())
  }
}
.height(300)    // 固定高度——故意限制
.clip(true)     // 超出裁剪

关键:300vp 高度只够显示约 5 个卡片,剩余 15 个被裁剪。

有 Scroll 版本
Scroll() {
  Column({ space: 6 }) {
    ForEach(this.GenerateItems(), (item: CardItem) => {
      this.CardItemView(item)
    }, (item: CardItem) => item.id.toString())
  }
}
.height(300)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.edgeEffect(EdgeEffect.Spring)

改动仅两点:外层从 Flex 换成 Scroll,去掉 .clip(true)。效果迥异。

@Builder 拆分

示例将 UI 拆分为多个 @Builder 函数:TitleSectionDescriptionSectionTabBarNonScrollViewScrollViewCardItemView。每个函数职责单一,两个对比视图共享相同的卡片渲染逻辑。

数据生成

GenerateItems() 生成 20 个卡片,每个包含 id、title、desc、color。20 个标题覆盖 Scroll 的不同知识点——从基础用法到性能优化,每个卡片本身就是一个知识点。


五、运行效果

首页

简洁入口页面,展示"Scroll 可滚动容器"卡片,点击导航到演示页。

Tab 0:「无 Scroll」

红色提示条:⚠️ Column 内容超出容器高度,底部被裁剪。只显示前 5 个卡片,其余不可见,无任何滚动响应。

Tab 1:「有 Scroll」

绿色提示条:✅ Scroll 包裹 Column,滑动可查看所有内容。右侧出现滚动条,手指滑动可流畅查看所有 20 个卡片,顶部/底部有弹性回弹效果。


六、Scroll 使用场景指南

6.1 何时用 Scroll?

场景需要 Scroll说明
固定高度的 Column 内容不确定内容多时自动滚动
全屏页面,内容刚好一屏无溢出
全屏页面,内容可能超出一屏最常见场景
Tab 内容页各 Tab 长度不同
横向卡片轮播用 Horizontal 方向
文章阅读页内容长度不确定
设置页/配置列表选项可能很多

6.2 Scroll vs 其他滚动方案

方案适用场景特点
Scroll内容量中等(< 50 项)轻量灵活,任意组件混排
List超长列表(50+ 项)LazyForEach 懒加载,性能最优
Grid网格布局适合相册、商品展示
Swiper轮播翻页一次一屏,支持自动轮播
Tabs多 Tab 页签结合 TabBar 使用

选型建议: 内容量 < 50 项用 Scroll;≥ 50 项用 List + LazyForEach;网格用 Grid


七、开发注意事项(API 24)

7.1 必须设置高度/宽度

Scroll 必须有明确的高度(纵向)或宽度(横向),否则滚动无效。

// ✅ 正确
Scroll() { /* ... */ }.height(300)
Scroll() { /* ... */ }.layoutWeight(1)

// ❌ 错误:无高度约束,内容无限延伸
Scroll() { /* ... */ }

7.2 避免嵌套冲突

垂直方向的 Scroll 不要直接嵌套:

// ❌ 错误
Scroll() { Scroll() { /* ... */ }.scrollable(ScrollDirection.Vertical) }
  .scrollable(ScrollDirection.Vertical)

手势冲突,体验差。如需嵌套,使用不同方向或 NestedScroll

7.3 Scroller 生命周期

struct MyPage {
  private scroller: Scroller = new Scroller();
  // 不要在 @Builder 中重复创建
}

7.4 性能考量

Scroll 一次性渲染所有子组件。对超长列表(数百项),改用 List + LazyForEach 实现虚拟化渲染。


八、横向滚动示例

Scroll() {
  Row({ space: 12 }) {
    ForEach(items, (item) => {
      Text(item.title).fontSize(16)
    })
  }
  .padding(16)
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Auto)
.edgeEffect(EdgeEffect.Spring)
.height(100)

只需将 scrollable 设为 Horizontal,内部容器改用 Row


九、Scroll 与 List 的取舍

维度ScrollList
渲染一次性全部渲染懒加载(仅渲染可见项)
数据量< 50 项50+ 项
子组件任意组件混排通常同类型
内置功能仅滚动选中、滑动删除、拖拽排序
复杂度

一句话: 内容少且结构灵活用 Scroll;超长列表用 List。


十、完整代码概览

10.1 演示页面入口

@Entry
@Component
struct ScrollDemo {
  @State itemCount: number = 20;
  @State selectedTab: number = 0;

  build() {
    Column() {
      this.TitleSection();
      this.DescriptionSection();
      this.TabBar();
      Stack() {
        if (this.selectedTab === 0) {
          this.NonScrollView();
        } else {
          this.ScrollView();
        }
      }
      .layoutWeight(1)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#F5F5F5')
  }
  // ... @Builder 方法
}

10.2 路由注册

{
  "src": [
    "pages/Index",
    "pages/ScrollDemo"
  ]
}

完整源码详见项目中的 ScrollDemo.etsIndex.ets


十一、实战练习

练习 1: 横向滚动专辑展示——Scroll + Row,10 个 120×120 卡片,左右滑动。

练习 2: 滚动控制按钮——使用 Scroller,添加"回到顶部"和"跳到底部"按钮。

练习 3: 滚动驱动动画——监听 onScroll,根据 yOffset 动态改变标题栏透明度。

练习 4: 嵌套滚动——创建搜索栏 + 水平 Tab + 垂直内容列表的复合页面。


十二、常见问题 FAQ

Q1:Scroll 不滚动怎么办?

检查:①是否设置固定高度/layoutWeight;②内容是否超过容器尺寸;③scrollable 是否为 None;④有无手势冲突。

Q2:滚动条太难看,怎么改?

可用 scrollBarWidth 设宽度,或 BarState.Off 隐藏原生条,自定义模拟滚动条。

Q3:如何监听滚动偏移量?

Scroll() { /* ... */ }
.onScroll((x, y) => console.info(`已滚动:${y}vp`))

Q4:Scroll 中点击事件不生效?

检查 onClick 是否被手势事件拦截,尝试 .hitTestBehavior(HitTestMode.Default)

Q5:API 24 和 API 23 区别?

核心 API 一致,API 24 增强 NestedScroll 支持和性能优化,代码完全兼容。


十三、总结

本文从内容溢出问题出发,介绍了鸿蒙 ArkTS 中 Scroll 容器的核心概念、使用方法、属性和最佳实践。

核心要点:

  1. Scroll 是最基础的可滚动容器,内容超出时外层包一层 Scroll 即可。
  2. 三个核心属性:scrollable(方向)、scrollBar(滚动条)、edgeEffect(边缘效果)。
  3. 必须设置高度/宽度,否则滚动无效。
  4. 对比展示是最直观的教学方式——无 Scroll vs 有 Scroll 一目了然。
  5. 中小量内容用 Scroll,超长列表用 List。

Scroll 是构建任何非平凡页面都绕不开的基础组件。掌握了它,你就掌握了鸿蒙布局系统的"溢出解决方案"。


参考资料: HarmonyOS NEXT API 24 官方文档 · 示例源码:entry/src/main/ets/pages/ScrollDemo.ets
本文发布于 2026 年 6 月,基于 HarmonyOS NEXT API 24,已在 DevEco Studio 验证。

更多推荐