【共创季稿事节】鸿蒙原生 ArkTS 布局方式之 Scroll 可滚动容器入门:何时需要 Scroll
鸿蒙原生 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 中:
Column和Row不会自动滚动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 时更新 selectedTab,Stack 保持两个视图位置一致,切换无缝。
无 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 函数:TitleSection、DescriptionSection、TabBar、NonScrollView、ScrollView、CardItemView。每个函数职责单一,两个对比视图共享相同的卡片渲染逻辑。
数据生成
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 的取舍
| 维度 | Scroll | List |
|---|---|---|
| 渲染 | 一次性全部渲染 | 懒加载(仅渲染可见项) |
| 数据量 | < 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.ets 和 Index.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 容器的核心概念、使用方法、属性和最佳实践。
核心要点:
- Scroll 是最基础的可滚动容器,内容超出时外层包一层 Scroll 即可。
- 三个核心属性:
scrollable(方向)、scrollBar(滚动条)、edgeEffect(边缘效果)。 - 必须设置高度/宽度,否则滚动无效。
- 对比展示是最直观的教学方式——无 Scroll vs 有 Scroll 一目了然。
- 中小量内容用 Scroll,超长列表用 List。
Scroll 是构建任何非平凡页面都绕不开的基础组件。掌握了它,你就掌握了鸿蒙布局系统的"溢出解决方案"。
参考资料: HarmonyOS NEXT API 24 官方文档 · 示例源码:
entry/src/main/ets/pages/ScrollDemo.ets
本文发布于 2026 年 6 月,基于 HarmonyOS NEXT API 24,已在 DevEco Studio 验证。
更多推荐


所有评论(0)