引言滚动条ScrollBar是移动应用中最不起眼但也最不可或缺的 UI 元素之一。它默默地位于屏幕边缘告诉用户你在这里和还有更多内容。大多数开发者使用系统默认的滚动条从未想过定制但当你构建一个阅读器、一个长列表页面、或者一个追求品牌一致性的产品时对滚动条的精细控制就变得至关重要。HarmonyOS NEXT 的 ArkUI 为 Scroll 组件提供了完整的滚动条定制能力——从显隐控制到颜色宽度从滚动事件追踪到编程式跳转。本文将通过构建一个功能完整的文档阅读器Demo全面覆盖这些能力在实际项目中的应用。读完本文你将能够掌握BarState三态显隐控制和适用场景使用scrollBarColor()和scrollBarWidth()定制视觉样式通过Scroller实现编程式滚动跳转利用.onScroll()事件构建阅读进度追踪系统实现章节自动定位和双向联动导航BarState滚动条的三种存在方式BarState是控制滚动条显隐行为的枚举定义在 ArkUI 的 Scroll 组件中。它有三种取值BarState.On — 始终可见Scroll(){...}.scrollBar(BarState.On)滚动条始终显示不论用户是否正在滚动。这种模式适合内容较长的文档/文章页面让用户随时感知位置数据表格等需要精确导航的场景桌面/平板等大屏设备滚动条不显得突兀本文的文档阅读器Demo 默认使用此模式。BarState.Off — 始终隐藏Scroll(){...}.scrollBar(BarState.Off)滚动条完全不可见。适合沉浸式阅读体验电子书、长文阅读全屏图片/视频浏览自定义滚动指示器用顶部的迷你进度条替代侧边滚动条BarState.Auto — 自动显隐默认Scroll(){...}.scrollBar(BarState.Auto)这是系统的默认行为滚动时短暂显示滚动条停止滚动后自动淡出隐藏。适合大多数常规场景。动态切换本文 Demo 通过底部 Toggle 开关让用户在BarState.On和BarState.Off之间自由切换StateshowSystemBar:booleantrue;getBarState():BarState{returnthis.showSystemBar?BarState.On:BarState.Off;}// UI 中Toggle({type:ToggleType.Switch,isOn:this.showSystemBar}).onChange((val:boolean){this.showSystemBarval;})这个交互让开发者可以直观对比两种模式的差异On 模式下右侧蓝色细线随时可见Off 模式下界面更干净但需要依赖顶部的阅读进度条来感知位置。自定义滚动条样式Beyond visibility control, ArkUI provides two style customization attributes:scrollBarColor — 自定义颜色Scroll(){...}.scrollBarColor(#1677FF)默认的系统滚动条通常是半透明灰色。通过scrollBarColor()你可以将滚动条颜色与应用的品牌色统一。本文 Demo 使用了蓝色#1677FF与顶部进度条和章节高亮按钮形成色彩一致。颜色选择建议浅色背景使用品牌的中间色调或#CCCCDD系列中性灰深色背景使用#FFFFFF44半透明白色彩色背景使用对比色或高明度的同色系scrollBarWidth — 自定义宽度Scroll(){...}.scrollBarWidth(4)默认滚动条宽度约 2-3vp。增加宽度到 4-6vp 可以让滚动条更容易被注意尤其在内容密集的页面中。本文 Demo 使用 4vp 宽度在显眼和不突兀之间找到平衡。宽度设计考量2-3vp极简风格适合现代设计语言4-5vp标准宽度可读性和美观兼顾6-8vp强调型适合老年人模式或触屏精度要求高的场景Scroller编程式滚动控制Scroller是 HarmonyOS 中实现代码控制滚动的核心对象。通过创建实例并绑定到 Scroll 组件开发者可以精确控制滚动行为。创建和绑定privatescroller:ScrollernewScroller();Scroll(this.scroller){...}scrollTo — 跳转到指定位置this.scroller.scrollTo({xOffset:0,yOffset:targetY});ScrollOptions要求同时提供xOffset和yOffset——这是 ArkTS 类型系统的严格性体现。在本文 Demo 中章节跳转按钮通过此方法将页面滚动到对应章节的锚点坐标scrollToChapter(idx:number):void{this.scroller.scrollTo({xOffset:0,yOffset:this.chapters[idx].anchorY});}每个章节预设一个锚点 Y 坐标概述0, 入门450, 进阶900, 实践1350, 总结1800点击按钮即刻跳转。其他 Scroller 方法scrollEdge(Edge.Top)— 滚动到顶部scrollEdge(Edge.Bottom)— 滚动到底部scrollPage({ next: true })— 向下翻一页scrollPage({ next: false })— 向上翻一页onScroll实时滚动追踪.onScroll()是 Scroll 组件的事件回调每次滚动位置变化时触发Scroll(this.scroller){...}.onScroll((xOffset:number,yOffset:number){this.scrollYyOffset;// 计算阅读进度letmaxScrollthis.totalHeight-viewportHeight;this.progressMath.min((yOffset/maxScroll)*100,100);// 更新当前章节this.updateActiveChapter(yOffset);})在本文 Demo 中.onScroll()承担了三个任务1. 更新阅读进度条进度条是一个位于标题栏下方的细线宽度百分比随滚动位置变化Row().width(this.progress.toString().concat(%)).height(3).backgroundColor(#1677FF)进度计算核心公式progressscrollY/(totalHeight-viewportHeight)×100其中totalHeight - viewportHeight是真正可滚动的范围。当scrollY 0时进度为 0%当scrollY达到最大值时进度为 100%。2. 更新底部状态信息底部状态栏实时显示当前滚动像素位置和阅读百分比Text(this.scrollY.toString().concat( px))Text(this.getProgressPercent().toString().concat(%))3. 自动定位当前章节通过比较当前 scrollY 与各章节锚点确定用户正在阅读的章节// onScroll 回调中letnewCh0;for(letithis.chapters.length-1;i0;i--){if(yOffsetthis.chapters[i].anchorY){newChi;break;}}if(newCh!this.activeChapter){this.activeChapternewCh;}算法从最后一个章节开始倒序查找效率更高因为用户通常从前往后读越后面的章节越早匹配到找到第一个锚点 ≤ 当前 scrollY 的章节即为当前章节。检测到章节变化时才更新activeChapter避免不必要的 UI 重绘。章节数据结构与锚点系统classChapter{title:string;anchorY:number;constructor(title:string,anchorY:number){this.titletitle;this.anchorYanchorY;}}privatechapters:Chapter[][newChapter(概述,0),newChapter(入门,450),newChapter(进阶,900),newChapter(实践,1350),newChapter(总结,1800),];锚点坐标是手动估算的——每个章节约 450vp 的内容高度。在生产项目中可以通过.onAreaChange()事件动态获取每个章节标题元素的实际 Y 坐标实现更精准的锚点定位。双向联动导航本文 Demo 实现了一种优雅的双向导航模式正向手动滚动 → 自动高亮用户用手指滚动正文 →onScroll检测 scrollY 变化 → 更新activeChapter→ 顶部章节按钮自动高亮切换。反向点击按钮 → 编程跳转用户点击章节按钮 →scrollToChapter(idx)调用scroller.scrollTo()→ 页面滚动到对应锚点 →onScroll再次触发 → 章节高亮更新确认一致性。这种双向联动创造了一种无论怎么操作导航状态始终正确的可靠体验。完整页面结构Column ├── Header标题 阅读百分比徽章 ├── Progress Bar3vp 高度蓝色线条 ├── Chapter Nav Bar水平排列的 5 个章节按钮 ├── Scroll正文内容区绑定 scroller onScroll │ ├── 概述章节标题 两段正文 │ ├── 入门章节两个小节 │ ├── 进阶章节两个小节 │ ├── 实践章节两个小节 │ └── 总结章节一段正文 └── Bottom Bar滚动位置 进度 滚动条 Toggle常见问题与解决方案问题一scrollTo 参数不完整报错Property xOffset is missing in type { yOffset: number; }原因ScrollOptions接口要求同时提供xOffset和yOffset。解决同时传递两个参数水平滚动不需要时传 0。// 错误this.scroller.scrollTo({yOffset:500});// 正确this.scroller.scrollTo({xOffset:0,yOffset:500});问题二进度计算不准确原因直接用scrollY / totalHeight计算进度没考虑可视区域。解决使用scrollY / (totalHeight - viewportHeight)并加Math.min(..., 100)防溢出。问题三onScroll 触发过于频繁现象滚动时onScroll以 60fps 的频率触发如果回调中有复杂计算会影响性能。解决方案onScroll 回调中只做轻量级的状态赋值和简单数学运算避免在回调中触发额外的animateTo或复杂遍历。总结本文通过构建一个文档阅读器Demo完整覆盖了 HarmonyOS ScrollBar 定制的五个核心维度BarState 显隐控制On / Off / Auto 三种模式及适用场景视觉样式定制scrollBarColor()和scrollBarWidth()实现品牌一致性Scroller 编程跳转scrollTo()实现章节导航onScroll 事件追踪实时计算阅读进度和章节定位双向联动导航手动滚动与编程跳转的协调配合这些技术组合起来可以应用于电子书阅读器、新闻客户端、技术文档站、帮助中心等多种实际产品场景。在掌握了本文的基础定制能力后你还可以进一步探索自定义滚动条形状通过完全隐藏系统滚动条 自绘指示器、基于物理参数的惯性滚动调节、以及结合动画系统的平滑滚动过渡效果。
鸿蒙新特性:ScrollBar 滚动条定制与文档阅读器实战
发布时间:2026/7/10 7:15:15
引言滚动条ScrollBar是移动应用中最不起眼但也最不可或缺的 UI 元素之一。它默默地位于屏幕边缘告诉用户你在这里和还有更多内容。大多数开发者使用系统默认的滚动条从未想过定制但当你构建一个阅读器、一个长列表页面、或者一个追求品牌一致性的产品时对滚动条的精细控制就变得至关重要。HarmonyOS NEXT 的 ArkUI 为 Scroll 组件提供了完整的滚动条定制能力——从显隐控制到颜色宽度从滚动事件追踪到编程式跳转。本文将通过构建一个功能完整的文档阅读器Demo全面覆盖这些能力在实际项目中的应用。读完本文你将能够掌握BarState三态显隐控制和适用场景使用scrollBarColor()和scrollBarWidth()定制视觉样式通过Scroller实现编程式滚动跳转利用.onScroll()事件构建阅读进度追踪系统实现章节自动定位和双向联动导航BarState滚动条的三种存在方式BarState是控制滚动条显隐行为的枚举定义在 ArkUI 的 Scroll 组件中。它有三种取值BarState.On — 始终可见Scroll(){...}.scrollBar(BarState.On)滚动条始终显示不论用户是否正在滚动。这种模式适合内容较长的文档/文章页面让用户随时感知位置数据表格等需要精确导航的场景桌面/平板等大屏设备滚动条不显得突兀本文的文档阅读器Demo 默认使用此模式。BarState.Off — 始终隐藏Scroll(){...}.scrollBar(BarState.Off)滚动条完全不可见。适合沉浸式阅读体验电子书、长文阅读全屏图片/视频浏览自定义滚动指示器用顶部的迷你进度条替代侧边滚动条BarState.Auto — 自动显隐默认Scroll(){...}.scrollBar(BarState.Auto)这是系统的默认行为滚动时短暂显示滚动条停止滚动后自动淡出隐藏。适合大多数常规场景。动态切换本文 Demo 通过底部 Toggle 开关让用户在BarState.On和BarState.Off之间自由切换StateshowSystemBar:booleantrue;getBarState():BarState{returnthis.showSystemBar?BarState.On:BarState.Off;}// UI 中Toggle({type:ToggleType.Switch,isOn:this.showSystemBar}).onChange((val:boolean){this.showSystemBarval;})这个交互让开发者可以直观对比两种模式的差异On 模式下右侧蓝色细线随时可见Off 模式下界面更干净但需要依赖顶部的阅读进度条来感知位置。自定义滚动条样式Beyond visibility control, ArkUI provides two style customization attributes:scrollBarColor — 自定义颜色Scroll(){...}.scrollBarColor(#1677FF)默认的系统滚动条通常是半透明灰色。通过scrollBarColor()你可以将滚动条颜色与应用的品牌色统一。本文 Demo 使用了蓝色#1677FF与顶部进度条和章节高亮按钮形成色彩一致。颜色选择建议浅色背景使用品牌的中间色调或#CCCCDD系列中性灰深色背景使用#FFFFFF44半透明白色彩色背景使用对比色或高明度的同色系scrollBarWidth — 自定义宽度Scroll(){...}.scrollBarWidth(4)默认滚动条宽度约 2-3vp。增加宽度到 4-6vp 可以让滚动条更容易被注意尤其在内容密集的页面中。本文 Demo 使用 4vp 宽度在显眼和不突兀之间找到平衡。宽度设计考量2-3vp极简风格适合现代设计语言4-5vp标准宽度可读性和美观兼顾6-8vp强调型适合老年人模式或触屏精度要求高的场景Scroller编程式滚动控制Scroller是 HarmonyOS 中实现代码控制滚动的核心对象。通过创建实例并绑定到 Scroll 组件开发者可以精确控制滚动行为。创建和绑定privatescroller:ScrollernewScroller();Scroll(this.scroller){...}scrollTo — 跳转到指定位置this.scroller.scrollTo({xOffset:0,yOffset:targetY});ScrollOptions要求同时提供xOffset和yOffset——这是 ArkTS 类型系统的严格性体现。在本文 Demo 中章节跳转按钮通过此方法将页面滚动到对应章节的锚点坐标scrollToChapter(idx:number):void{this.scroller.scrollTo({xOffset:0,yOffset:this.chapters[idx].anchorY});}每个章节预设一个锚点 Y 坐标概述0, 入门450, 进阶900, 实践1350, 总结1800点击按钮即刻跳转。其他 Scroller 方法scrollEdge(Edge.Top)— 滚动到顶部scrollEdge(Edge.Bottom)— 滚动到底部scrollPage({ next: true })— 向下翻一页scrollPage({ next: false })— 向上翻一页onScroll实时滚动追踪.onScroll()是 Scroll 组件的事件回调每次滚动位置变化时触发Scroll(this.scroller){...}.onScroll((xOffset:number,yOffset:number){this.scrollYyOffset;// 计算阅读进度letmaxScrollthis.totalHeight-viewportHeight;this.progressMath.min((yOffset/maxScroll)*100,100);// 更新当前章节this.updateActiveChapter(yOffset);})在本文 Demo 中.onScroll()承担了三个任务1. 更新阅读进度条进度条是一个位于标题栏下方的细线宽度百分比随滚动位置变化Row().width(this.progress.toString().concat(%)).height(3).backgroundColor(#1677FF)进度计算核心公式progressscrollY/(totalHeight-viewportHeight)×100其中totalHeight - viewportHeight是真正可滚动的范围。当scrollY 0时进度为 0%当scrollY达到最大值时进度为 100%。2. 更新底部状态信息底部状态栏实时显示当前滚动像素位置和阅读百分比Text(this.scrollY.toString().concat( px))Text(this.getProgressPercent().toString().concat(%))3. 自动定位当前章节通过比较当前 scrollY 与各章节锚点确定用户正在阅读的章节// onScroll 回调中letnewCh0;for(letithis.chapters.length-1;i0;i--){if(yOffsetthis.chapters[i].anchorY){newChi;break;}}if(newCh!this.activeChapter){this.activeChapternewCh;}算法从最后一个章节开始倒序查找效率更高因为用户通常从前往后读越后面的章节越早匹配到找到第一个锚点 ≤ 当前 scrollY 的章节即为当前章节。检测到章节变化时才更新activeChapter避免不必要的 UI 重绘。章节数据结构与锚点系统classChapter{title:string;anchorY:number;constructor(title:string,anchorY:number){this.titletitle;this.anchorYanchorY;}}privatechapters:Chapter[][newChapter(概述,0),newChapter(入门,450),newChapter(进阶,900),newChapter(实践,1350),newChapter(总结,1800),];锚点坐标是手动估算的——每个章节约 450vp 的内容高度。在生产项目中可以通过.onAreaChange()事件动态获取每个章节标题元素的实际 Y 坐标实现更精准的锚点定位。双向联动导航本文 Demo 实现了一种优雅的双向导航模式正向手动滚动 → 自动高亮用户用手指滚动正文 →onScroll检测 scrollY 变化 → 更新activeChapter→ 顶部章节按钮自动高亮切换。反向点击按钮 → 编程跳转用户点击章节按钮 →scrollToChapter(idx)调用scroller.scrollTo()→ 页面滚动到对应锚点 →onScroll再次触发 → 章节高亮更新确认一致性。这种双向联动创造了一种无论怎么操作导航状态始终正确的可靠体验。完整页面结构Column ├── Header标题 阅读百分比徽章 ├── Progress Bar3vp 高度蓝色线条 ├── Chapter Nav Bar水平排列的 5 个章节按钮 ├── Scroll正文内容区绑定 scroller onScroll │ ├── 概述章节标题 两段正文 │ ├── 入门章节两个小节 │ ├── 进阶章节两个小节 │ ├── 实践章节两个小节 │ └── 总结章节一段正文 └── Bottom Bar滚动位置 进度 滚动条 Toggle常见问题与解决方案问题一scrollTo 参数不完整报错Property xOffset is missing in type { yOffset: number; }原因ScrollOptions接口要求同时提供xOffset和yOffset。解决同时传递两个参数水平滚动不需要时传 0。// 错误this.scroller.scrollTo({yOffset:500});// 正确this.scroller.scrollTo({xOffset:0,yOffset:500});问题二进度计算不准确原因直接用scrollY / totalHeight计算进度没考虑可视区域。解决使用scrollY / (totalHeight - viewportHeight)并加Math.min(..., 100)防溢出。问题三onScroll 触发过于频繁现象滚动时onScroll以 60fps 的频率触发如果回调中有复杂计算会影响性能。解决方案onScroll 回调中只做轻量级的状态赋值和简单数学运算避免在回调中触发额外的animateTo或复杂遍历。总结本文通过构建一个文档阅读器Demo完整覆盖了 HarmonyOS ScrollBar 定制的五个核心维度BarState 显隐控制On / Off / Auto 三种模式及适用场景视觉样式定制scrollBarColor()和scrollBarWidth()实现品牌一致性Scroller 编程跳转scrollTo()实现章节导航onScroll 事件追踪实时计算阅读进度和章节定位双向联动导航手动滚动与编程跳转的协调配合这些技术组合起来可以应用于电子书阅读器、新闻客户端、技术文档站、帮助中心等多种实际产品场景。在掌握了本文的基础定制能力后你还可以进一步探索自定义滚动条形状通过完全隐藏系统滚动条 自绘指示器、基于物理参数的惯性滚动调节、以及结合动画系统的平滑滚动过渡效果。