

前言
前几篇我们用 Column/Row/Blank/layoutWeight 搭好了 HUD、底部栏、棋盘——所有内容都一屏装得下。但实战中经常遇到内容超出屏幕的场景:游戏规则说明太长、战绩历史几十条、设置页十几项。这时候需要滚动容器让用户上下/左右滑动查看。
HarmonyOS 提供了 Scroll 滚动容器 + Scroller 滚动控制器——前者负责可滚动区域,后者负责编程式滚动(scrollTo、scrollEdge)。本篇以「猫猫大作战」规则说明面板扩展为锚点,把 Scroll 容器、Scroller 控制器、滚动监听与性能三大要点讲透。
提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–24 篇。本篇是布局进阶的第五篇。
一、场景拆解:规则面板需要滚动
回顾「猫猫大作战」主菜单的规则面板(第 4 篇):
// 来源:entry/src/main/ets/pages/Index.ets MainMenuView() 规则面板
Column() {
Text('游戏规则').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#2C3E50')
Text('• 点击列投放猫咪').fontSize(13).fontColor('#7F8C8D')
Text('• 相邻同级猫咪自动合并升级').fontSize(13).fontColor('#7F8C8D')
Text('• 连续合并触发连击加分').fontSize(13).fontColor('#7F8C8D')
Text('• 猫咪堆到顶部则游戏结束').fontSize(13).fontColor('#7F8C8D')
}
.width('80%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.7)')
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
现在规则扩展到 10 条,Column 高度撑爆屏幕,底部按钮被推出可视区。需要给规则面板套一层 Scroll 让它可上下滚动。
Scroll 的解法:
Scroll() {
Column() {
/* 10 条规则 */
}
}
.scrollable(ScrollDirection.Vertical) // 纵向滚动
.scrollBar(BarState.Auto) // 自动显示滚动条
.width('80%')
.height(200) // 固定高度,超出滚动
}
关键经验:Scroll 必须设固定 height——不设高度,Scroll 会撑开到内容全高,失去滚动意义。
二、Scroll 滚动容器
2.1 基本结构
Scroll() {
Column() { // 或 Row,只能有一个直接子组件
/* 内容 */
}
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.width('100%')
.height(200)
核心约束:
| 只能有一个直接子组件 | 多个需用 Column/Row 包裹 |
| 必须设 height(纵向) | 否则撑开到内容全高,不滚动 |
| 必须设 width(横向) | 否则撑开到内容全宽 |
| 内容超出容器尺寸才会滚动 | 内容短不滚动 |
2.2 scrollable 滚动方向
.scrollable(ScrollDirection.Vertical) // 纵向(默认)
.scrollable(ScrollDirection.Horizontal) // 横向
| Vertical | 列表、文章 | 必须设 |
| Horizontal | 横向轮播、Tab 条 | 必须设 width |
2.3 scrollBar 滚动条
.scrollBar(BarState.Auto) // 自动:滚动时显示,停止后淡出
.scrollBar(BarState.On) // 常显:一直显示
.scrollBar(BarState.Off) // 隐藏:不显示滚动条
实战经验:游戏内规则面板用 BarState.Auto——滚动时给视觉反馈,不滚动时不打扰。
三、Scroller 滚动控制器
3.1 创建 Scroller
private scroller: Scroller = new Scroller();
Scroll(this.scroller) { // 把 Scroller 传给 Scroll
Column() { /* … */ }
}
.height(200)
Scroller 是一个控制器对象,传给 Scroll 后,就能用它的 API 编程式控制滚动。
3.2 scrollTo 滚动到指定位置
this.scroller.scrollTo({
xOffset: 0, // 横向偏移(vp)
yOffset: 100, // 纵向偏移(vp):向下滚 100vp
animation: { duration: 300, curve: Curve.EaseOut } // 带动画
})
场景:点击「跳到第 5 条规则」按钮,平滑滚到对应位置。
3.3 scrollEdge 滚动到边缘
this.scroller.scrollEdge(Edge.Top) // 滚到顶
this.scroller.scrollEdge(Edge.Bottom) // 滚到底
场景:聊天页收到新消息自动滚到底部。
3.4 scrollToIndex 滚到指定项(需配合 List)
// Scroller 主要配合 List 使用
List({ scroller: this.scroller }) { /* … */ }
this.scroller.scrollToIndex(10) // 滚到第 10 项
提示:scrollToIndex 主要用于 List 组件,普通 Scroll 用 scrollTo。本系列第 68 篇会专讲 LazyForEach 大列表。
3.5 currentOffset 获取当前偏移
const offset = this.scroller.currentOffset();
console.info(`x: ${offset.xOffset}, y: ${offset.yOffset}`);
场景:根据滚动位置显示「回到顶部」按钮——yOffset > 200 时显示。
四、用 Scroll 改造规则面板
4.1 改造为可滚动规则面板
@Builder
MainMenuView() {
Column() {
Spacer().height('15%')
// 游戏标题
Text('🐱').fontSize(72).margin({ bottom: 8 })
Text('猫猫大作战').fontSize(36).fontWeight(FontWeight.Bold).fontColor('#2C3E50').margin({ bottom: 8 })
Text('合并进化 · 策略消除').fontSize(16).fontColor('#95A5A6').margin({ bottom: 48 })
// 最高分
if (this.highScore > 0) {
Row() {
Text('🏆 最高分: ').fontSize(16).fontColor('#F1C40F')
Text(this.highScore.toString()).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#F1C40F')
}.margin({ bottom: 32 })
}
// 开始游戏按钮
Button('开始游戏')
.width('70%').height(56)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF').backgroundColor('#2ECC71')
.borderRadius(28)
.shadow({ radius: 8, color: 'rgba(46, 204, 113, 0.4)', offsetY: 4 })
.onClick(() => { this.startGame(); })
Spacer().height(24)
// 游戏规则面板(本篇改造:套 Scroll 让规则可滚动)
Scroll() {
Column() {
Text('游戏规则')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#2C3E50')
.margin({ bottom: 8 })
Text('• 点击列投放猫咪').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 相邻同级猫咪自动合并升级').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 连续合并触发连击加分').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 猫咪堆到顶部则游戏结束').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 同级猫咪相邻 2 个自动合并').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 合并后等级 +1,得分按 3^n 增长').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 连击窗口 1.5s,连续合并倍率叠加').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 最高等级传奇猫,得分 2430').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 自动生成间隔 2s,最多 40 只').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 棋盘 5 列 8 行,第 2 行堆积则结束').fontSize(13).fontColor('#7F8C8D')
}
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.width('80%')
.height(180) // 固定 180vp 高,超出滚动
.padding(16)
.backgroundColor('rgba(255,255,255,0.7)')
.borderRadius(12)
Spacer()
}
.width('100%').height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#E8F4F8', 0.0], ['#D6EEF5', 0.5], ['#C9E8F2', 1.0]]
})
.alignItems(HorizontalAlign.Center)
}
改造对比:
| 规则条数 | 4 条 | 10 条 |
| 高度 | 撑开到内容全高 | 固定 180vp |
| 超出处理 | 顶出底部按钮 | 内部滚动 |
| 滚动条 | 无 | Auto 自动显示 |
4.2 关键:height(180) 固定高度
Scroll() { /* 10 条规则 */ }
.height(180) // 固定高度
规则:内容总高 > 180vp 时滚动;内容总高 < 180vp 时不滚动(Scroll 撑开到内容高)。
踩坑:如果不设 height,Scroll 会撑开到 10 条规则的全高(约 400vp),把底部按钮顶出屏幕——这时 Scroll 等于普通 Column,失去滚动能力。
五、Scroll 与 Scroller 配合:跳到顶部按钮
5.1 场景
规则面板滚到底部后,用户想快速回到顶部。我们在面板右上角放一个「↑」按钮,点击调用 scroller.scrollEdge(Edge.Top) 平滑滚到顶。
5.2 实现
@Entry
@Component
struct Index {
private ruleScroller: Scroller = new Scroller();
@State showBackToTop: boolean = false; // 是否显示「回顶」按钮
@Builder
MainMenuView() {
Column() {
/* 标题、按钮等 */
// 规则面板(带 Scroller)
Stack() {
Scroll(this.ruleScroller) {
Column() { /* 10 条规则 */ }
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.onScroll((xOffset: number, yOffset: number) => {
// 滚动超过 100vp 显示回顶按钮
this.showBackToTop = yOffset > 100;
})
.width('80%')
.height(180)
.padding(16)
.backgroundColor('rgba(255,255,255,0.7)')
.borderRadius(12)
// 右上角回顶按钮
if (this.showBackToTop) {
Button('↑')
.width(32).height(32)
.fontSize(18).fontColor('#FFFFFF')
.backgroundColor('rgba(44, 62, 80, 0.6)')
.borderRadius(16)
.position({ x: '85%', y: 8 }) // 右上角
.onClick(() => {
this.ruleScroller.scrollEdge(Edge.Top);
})
}
}
.width('80%').height(180)
}
}
}
执行流程:
5.3 onScroll 回调
.onScroll((xOffset: number, yOffset: number) => {
console.info(`滚动偏移: x=${xOffset}, y=${yOffset}`);
})
| xOffset | 横向滚动偏移(vp) |
| yOffset | 纵向滚动偏移(vp) |
注意:onScroll 滚动时高频触发(每帧一次),回调内不要做重计算。
六、Scroll 性能优化
6.1 Scroll vs List 的取舍
| 子组件 | 一个(Column/Row 包内容) | 多个 ListItem |
| 复用机制 | ❌ 无,全部渲染 | ✅ LazyForEach 按需渲染 |
| 适合 | 内容条数固定且少(规则面板、说明页) | 内容条数多或动态(聊天、战绩列表) |
| 性能 | 条数多时卡顿 | 千条流畅 |
实战经验:条数 < 20 用 Scroll,条数 ≥ 20 用 List + LazyForEach。本系列第 68 篇会专讲 LazyForEach 大列表。
6.2 避免嵌套 Scroll
// ❌ 错误:嵌套 Scroll,手势冲突
Scroll() {
Scroll() { /* … */ }
}
// ✅ 正确:用 List 嵌套,或用 Scroll + Column 分段
Scroll() {
Column() {
HeaderSection()
ContentSection()
}
}
6.3 contentScrollEnabled 动态禁滚
某些场景需要临时禁用滚动(如内容正在加载):
Scroll() { /* … */ }
.enabled(this.isContentReady) // 内容未就绪时禁滚
七、完整代码:可滚动规则面板
// 改造版:规则面板套 Scroll,10 条规则可滚动
@Builder
MainMenuView() {
Column() {
Spacer().height('15%')
Text('🐱').fontSize(72).margin({ bottom: 8 })
Text('猫猫大作战').fontSize(36).fontWeight(FontWeight.Bold).fontColor('#2C3E50').margin({ bottom: 8 })
Text('合并进化 · 策略消除').fontSize(16).fontColor('#95A5A6').margin({ bottom: 48 })
if (this.highScore > 0) {
Row() {
Text('🏆 最高分: ').fontSize(16).fontColor('#F1C40F')
Text(this.highScore.toString()).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#F1C40F')
}.margin({ bottom: 32 })
}
Button('开始游戏')
.width('70%').height(56)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF').backgroundColor('#2ECC71')
.borderRadius(28)
.shadow({ radius: 8, color: 'rgba(46, 204, 113, 0.4)', offsetY: 4 })
.onClick(() => { this.startGame(); })
Spacer().height(24)
// 规则面板(Scroll 改造)
Scroll() {
Column() {
Text('游戏规则')
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#2C3E50')
.margin({ bottom: 8 })
Text('• 点击列投放猫咪').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 相邻同级猫咪自动合并升级').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 连续合并触发连击加分').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 猫咪堆到顶部则游戏结束').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 同级猫咪相邻 2 个自动合并').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 合并后等级 +1,得分按 3^n 增长').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 连击窗口 1.5s,连续合并倍率叠加').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 最高等级传奇猫,得分 2430').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 自动生成间隔 2s,最多 40 只').fontSize(13).fontColor('#7F8C8D').margin({ bottom: 4 })
Text('• 棋盘 5 列 8 行,第 2 行堆积则结束').fontSize(13).fontColor('#7F8C8D')
}
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.width('80%')
.height(180)
.padding(16)
.backgroundColor('rgba(255,255,255,0.7)')
.borderRadius(12)
Spacer()
}
.width('100%').height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [['#E8F4F8', 0.0], ['#D6EEF5', 0.5], ['#C9E8F2', 1.0]]
})
.alignItems(HorizontalAlign.Center)
}
八、踩坑提示
8.1 Scroll 不设 height 不滚动
// ❌ 错误:没设 height,Scroll 撑开到内容全高
Scroll() { Column() { /* 10 条规则 */ } }
// 内容全显示,不滚动
// ✅ 正确:设固定 height
Scroll() { /* … */ }.height(180)
8.2 Scroll 内多个直接子组件
// ❌ 错误:多个直接子组件,只渲染第一个
Scroll() {
Text('A')
Text('B')
}
// ✅ 正确:用 Column 包裹
Scroll() {
Column() {
Text('A')
Text('B')
}
}
8.3 onScroll 回调内改 state 卡顿
// ❌ 错误:每帧改 state,触发重渲染,卡顿
.onScroll((_, yOffset) => {
this.currentY = yOffset; // 高频改 state
})
// ✅ 正确:节流,或只在阈值跨越时改
.onScroll((_, yOffset) => {
const shouldShow = yOffset > 100;
if (shouldShow !== this.showBackToTop) {
this.showBackToTop = shouldShow; // 只在状态变化时改
}
})
九、调试技巧
十、性能与最佳实践
总结
本篇我们从 Scroll 滚动容器切入,掌握了Scroll 基本结构(必须设 height)、Scroller 控制器(scrollTo/scrollEdge/currentOffset)、onScroll 滚动监听与回顶按钮、Scroll vs List 取舍四大要点,并给出了可滚动规则面板完整代码。核心要点:Scroll 设固定 height 才滚动;Scroller 编程式控制;onScroll 高频回调别改 state;条数多用 List。
下一篇我们将拆解 Badge——消息角标的实现。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 「猫猫大作战」项目源码:本仓库 entry/src/main/ets/pages/Index.ets
- Scroll 滚动容器官方指南
- Scroller 滚动控制器官方指南
- List 列表组件官方指南
- ArkUI 滚动性能最佳实践
- 开源鸿蒙跨平台社区
- HarmonyOS 开发者官方文档首页
- 系列索引:本仓库 articles/INDEX.md
网硕互联帮助中心








评论前必须登录!
注册