【共创季稿事节】鸿蒙原生 ArkTS 布局方式之 Scroll + friction 惯性滑动 HarmonyOS NEXT · ArkTS · Scroll 容器 · friction 摩擦系数 ·



一、引言
在移动端应用的日常开发中,列表滚动是最基础、最高频的交互场景之一。用户用手指在屏幕上轻轻一划,列表随之惯性滑动——这个看起来理所当然的「惯性滑动」效果,其背后涉及的摩擦系数控制机制,实际上大有文章可做。
从 HarmonyOS API 12(HarmonyOS NEXT)开始,Scroll 容器的摩擦系数 API 发生了重要变化:旧版独立的 flingFriction 属性被移除,统一合并到 friction 属性中。这一变更意味着开发者不再需要分别配置"手指拖拽时的摩擦力"和"松手后惯性滑动的摩擦力",而是通过一个统一的 friction 值来控制整个滚动行为。
本文将通过一个完整的可运行示例项目,深度剖析 HarmonyOS NEXT 中 Scroll + friction 惯性滑动布局的实现原理、API 用法、最佳实践,以及从旧版迁移到新版的注意事项。
二、背景知识
2.1 Scroll 容器概述
Scroll 是 HarmonyOS ArkTS 中最核心的可滚动容器组件,它允许内容在垂直或水平方向上滚动。其基本用法如下:
Scroll(scroller?: Scroller) {
// 可滚动的内容
Column() {
// 子组件列表
}
}
.scrollable(ScrollDirection.Vertical) // 滚动方向
.edgeEffect(EdgeEffect.Spring) // 边缘回弹
.scrollBar(BarState.Off) // 滚动条状态
2.2 什么决定惯性滑行的距离?
当用户在屏幕上快速滑动后松手,列表不会立即停止,而是会继续惯性滑行一段距离,然后慢慢停下来。这段距离由什么决定?答案是 摩擦系数(friction)。
摩擦系数的物理含义可以这样理解:
friction 值 物理类比 滑动表现
0.1 ~ 0.2 🛹 冰面 / 抛光大理石 轻轻一划飞出很远
0.3 ~ 0.5 🚴 柏油路面 顺滑自然,日常推荐
0.6 ~ 0.8 🚶 砂石路面 阻尼感明显,精确控制
0.9 ~ 1.0 🧱 泥泞路面 松手即停,几乎无惯性
2.3 API 版本演进
理解 friction 的 API 变更历史,对从旧版迁移到 HarmonyOS NEXT 至关重要:
API 版本 属性名 默认值 说明
API 10 friction 0.6 仅控制手指拖拽摩擦力
API 11 friction 0.7 仍仅控制拖拽
API 11 flingFriction 0.6 独立控制惯性滑动摩擦力
API 12+ friction 0.75 统一控制拖拽 + 惯性滑动
⚠️ 重要提示:HarmonyOS NEXT(API 12+)中 flingFriction 已被移除。如果代码中仍使用 .flingFriction() 会导致编译错误 Property ‘flingFriction’ does not exist on type ‘ScrollAttribute’。迁移方式非常简单——直接删除 .flingFriction() 调用,通过调节 friction 的值来实现相同的效果范围即可。
三、项目结构
本文配套的示例代码位于 FrictionScroll.ets,项目结构如下:
entry/src/main/ets/pages/
├── Index.ets ← 首页,导航入口
├── FrostedGlass.ets ← 毛玻璃布局(另一个示例)
└── FrictionScroll.ets ← 本文主角:惯性滑动演示页
3.1 页面功能总览
运行后在首页点击「Scroll+friction 惯性滑动」进入演示页,页面从上到下依次为:
标题区 —— 说明页面用途
参数控制面板 —— Slider 滑块实时调节 friction 值(0.1 ~ 1.0)
快速预设按钮 —— 一键切换「冰面 0.2」「默认 0.5」「阻尼 0.9」
状态指示条 —— 显示当前滚动偏移量和滚动状态(静止/拖动中/惯性滑动)
对比演示区 —— 三个独立小 Scroll 并列,分别使用不同 friction,直观对比
主列表 —— 20 张彩色卡片,在统一 friction 下展示滚动效果
底部操作栏 —— 「回到顶部」和「重置参数」按钮
四、核心代码深度解析
4.1 状态定义
// 第 28 ~ 51 行
struct FrictionScroll {
@State frictionValue: number = 0.5; // 摩擦系数,核心状态
@State currentScrollOffset: number = 0.0; // 当前滚动偏移
@State scrollStateText: string = ‘● 静止’; // 滚动状态文本
@State isFlinging: boolean = false; // 是否惯性滑动中
private cardData: CardItem[] = []; // 色卡数据源
private demoFrictions: number[] = [0.2, 0.5, 0.9]; // 对比组
private scroller: Scroller = new Scroller(); // Scroll 控制器
}
值得关注的是 demoFrictions: number[] = [0.2, 0.5, 0.9],这个数组的三个值分别代表低/中/高三种摩擦场景,后续会被用在对比演示区中。
4.2 friction —— 唯一的核心 API
// 第 107 ~ 115 行
Scroll(this.scroller) {
// … 内容区域
}
.id(‘MainScroll’)
.width(‘100%’)
.layoutWeight(1)
// ★★★ 核心API: friction —— 摩擦系数 ★★★
// 同时控制手指拖拽和松手后惯性滑动的整体摩擦效果
.friction(this.frictionValue) // ← 绑定 @State 变量,实时生效
这是整个布局最关键的代码行。friction 接受一个 number | Resource 类型的参数,在 HarmonyOS NEXT 中:
值域: 0.1 ~ 1.0(建议范围,理论上可以更大,但超过 1.0 后效果变化不明显)
默认值: 0.75(API 12+)
效果: 同时影响手指拖动时的阻力和松手后惯性滑行的距离
绑定方式: 可以通过 @State 变量动态绑定,Slide 滑块改变 frictionValue 时,Scroll 容器会实时响应,无需重建组件
4.3 onWillScroll —— 监听滚动状态
// 第 124 ~ 151 行
.onWillScroll((xOffset: number, yOffset: number, scrollState: ScrollState): void => {
this.currentScrollOffset = yOffset;
switch (scrollState) {
case ScrollState.Idle:
this.scrollStateText = ‘● 静止’;
this.isFlinging = false;
break;
case ScrollState.Scroll:
this.scrollStateText = ‘☝ 手指拖动中’;
this.isFlinging = false;
break;
case ScrollState.Fling:
this.scrollStateText = ‘⚡ 惯性滑动中!’;
this.isFlinging = true; // ★ 关键:标记惯性滑动状态
break;
}
})
onWillScroll 在每次滚动前触发,其回调参数中的 scrollState 枚举是理解滚动行为的关键:
ScrollState 触发时机 典型应用
Idle 无滚动时 显示静态提示
Scroll 手指触屏拖动 / 鼠标滚轮 / 滚动条拖动 显示"拖动中"
Fling 松手后惯性滑行 显示"惯性滑动中",可在此禁用某些交互
这里把光标聚焦在 Fling 状态——这是「惯性滑动」最直接的证明。当手指离开屏幕后,ScrollState 会从 Scroll 切换为 Fling,直到惯性结束才回到 Idle。
4.4 对比演示区 —— 直观感受 friction 差异
为了帮助开发者直观理解不同 friction 值的效果差异,代码中实现了一个特殊的对比演示区:
// 第 355 ~ 382 行
@Builder
comparisonSection() {
Column() {
Text(‘⚡ friction 对比演示(分别拖拽感受差异)’)
.fontSize(12)
.fontWeight(FontWeight.Medium)
Row({ space: 8 }) {
ForEach(this.demoFrictions, (fric: number) => {
this.miniScrollDemo(fric) // ← 创建 3 个小 Scroll
})
}
.width('100%')
.height(130)
}
}
每个小 Scroll 都有自己的 Scroller 实例和独立的 friction 值:
// 第 384 ~ 433 行
@Builder
miniScrollDemo(frictionVal: number) {
Column() {
// 标签头,颜色随 friction 值变化
Row() { Text(f=${frictionVal.toFixed(1)}) }
.backgroundColor(frictionVal <= 0.3 ? ‘#10AC84’ :
frictionVal <= 0.7 ? ‘#0066FF’ : ‘#FF6B6B’)
// 独立 Scroll
Scroll(new Scroller()) {
Column() { /* 5 个子项 */ }
}
.width('100%')
.layoutWeight(1)
.friction(frictionVal) // ← 每个 Scroll 不同 friction
.edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true })
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
}
}
这个设计可以让开发者在同一屏幕、同一时间、用同一根手指,同时感受三个不同摩擦系数下的滚动手感差异,比单纯看数值更加直观。
4.5 参数控制面板
// 第 182 ~ 274 行
@Builder
controlPanel() {
Column() {
Row() {
Text(‘摩擦系数 friction’)
Text((${this.frictionValue.toFixed(2)}))
}
Slider({
value: this.frictionValue,
min: 0.1,
max: 1.0,
step: 0.05
})
.blockColor(/* 根据值变化颜色 */)
.onChange((val) => { this.frictionValue = val; })
// 进度条指示器 + 动态提示
Text(this.getFrictionHint())
}
}
这里有一个小细节很有价值:blockColor 和 selectedColor 根据 friction 值动态变色——低值时绿色(顺滑),中等时蓝色(均衡),高值时红色(阻尼)。用户不需要看具体数值,仅通过滑块颜色就能感知当前摩擦系数的档位。
4.6 色卡列表项
// 第 435 ~ 484 行
@Builder
cardItem(item: CardItem) {
Row() {
// 左侧色条
Column()
.width(6)
.height(‘80%’)
.backgroundColor(item.color)
.borderRadius({ topLeft: 4, bottomLeft: 4 })
// 右侧文字
Column({ space: 4 }) {
Text(item.title).fontSize(16)
Text(item.desc).fontSize(12).fontColor('#888')
}
.padding({ left: 12, right: 8, top: 14, bottom: 14 })
.layoutWeight(1)
}
.width(‘100%’)
.height(76)
.backgroundColor(‘#FFFFFF’)
.borderRadius(10)
.shadow({ radius: 6, color: ‘rgba(0,0,0,0.08)’ })
}
每个卡片左侧有一个 6vp 宽的色条,用 Column().backgroundColor() 替代了旧版 API 中不存在的 Rectangle 组件。卡片带有轻微的阴影,视觉上层次分明。
4.7 底部操作栏
// 第 486 ~ 523 行
@Builder
bottomActions() {
Row({ space: 12 }) {
Button(‘⬆ 回到顶部’)
.onClick(() => {
this.scroller.scrollTo({
xOffset: 0,
yOffset: 0,
animation: { duration: 400, curve: Curve.FastOutSlowIn }
});
})
Button('↻ 重置 friction=0.5')
.onClick(() => {
this.frictionValue = 0.5;
})
}
}
「回到顶部」按钮使用了 Scroller.scrollTo() 并配置了 400ms 的 FastOutSlowIn 动画曲线,这是 Material Design 推荐的运动曲线——快速启动、缓慢减速,视觉上最自然。
五、从旧版迁移到 HarmonyOS NEXT
5.1 需要修改的代码
如果您的项目从 API 11 迁移到 API 12+,以下是需要关注的变化:
旧版代码 (API 11)
Scroll() {
// 内容
}
.friction(0.7) // 拖拽摩擦
.flingFriction(0.6) // 惯性摩擦(独立 API)
新版代码 (API 12+)
Scroll() {
// 内容
}
.friction(0.75) // 统一摩擦系数
5.2 适配对照
旧版行为 旧版 API 新版 API 等效 friction 值
拖拽很涩、惯性很短 friction=0.9, flingFriction=0.9 friction=0.9 0.9
拖拽正常、惯性适中 friction=0.7, flingFriction=0.6 friction=0.5~0.65 0.5 ~ 0.65
拖拽轻快、惯性很远 friction=0.5, flingFriction=0.2 friction=0.2~0.35 0.2 ~ 0.35
💡 旧版 flingFriction 的作用是"惯性阶段的独立摩擦系数",合并后 friction 的值可以理解为两者的加权平均。如果需要更远的惯性滑行,整体降低 friction 即可。
5.3 常见编译错误
迁移中最常见的错误是:
Property ‘flingFriction’ does not exist on type ‘ScrollAttribute’.
解决方案:直接在代码中搜索 .flingFriction( 并删除该行调用,然后将 .friction() 的值适当调低 10%~20% 以补偿 flingFriction 的移除。
六、最佳实践与 friction 值选择指南
6.1 不同场景推荐值
应用场景 推荐 friction 原因
图片/相册浏览 0.2 ~ 0.3 图片浏览需要流畅的"甩动"翻页感
社交媒体信息流 0.4 ~ 0.5 兼顾信息浏览速度和交互可控性
新闻/文章列表 0.5 ~ 0.6 用户需要较精确地定位到某条新闻
设置页面/表单 0.7 ~ 0.9 精确控制,避免滑过头
地图/画布导航 0.1 ~ 0.2 极低摩擦,类似物理世界的惯性
6.2 Accessibility 考虑
对于需要无障碍支持的场景,建议:
提供预设选项(如"流畅模式"“标准模式”“精确模式”),对应不同的 friction 值
默认值建议使用 0.5 作为起步,既不过于灵敏也不过于迟钝
通过系统设置中的「减少动态效果」选项,可以相应提高 friction 值以减弱惯性
6.3 性能考量
friction 的修改是轻量级操作,不会触发组件重建,也不会有额外的布局计算。Slider 拖动过程中实时修改 friction 值不会产生性能瓶颈,可以放心使用双向绑定。
七、完整源代码
本文对应的完整源代码 FrictionScroll.ets 共 536 行,包含:
1 个 interface 定义(CardItem)
1 个 @Component struct(FrictionScroll)
7 个 @Builder 子组件
1 个工具方法
核心依赖导入:
import { promptAction } from ‘@kit.ArkUI’;
所有关键 API 一览表:
API 用途 参数类型
Scroll(scroller) 可滚动容器 Scroller
.friction(value) 摩擦系数(核心) number | Resource
.edgeEffect(Spring, options) 边缘回弹 EdgeEffect
.scrollable(Vertical) 滚动方向 ScrollDirection
.onWillScroll(callback) 滚动前回调 回调函数
.onScrollStop(callback) 滚动停止回调 回调函数
Scroller.scrollTo(options) 编程式滚动 偏移 + 动画配置
八、总结
本文通过一个完整的 Scroll + friction 惯性滑动示例应用,详细阐述了 HarmonyOS NEXT 中 Scroll 容器的摩擦系数控制机制。核心要点总结如下:
HarmonyOS NEXT (API 12+) 移除了独立的 flingFriction 属性,统一由 friction 控制手指拖拽和惯性滑动两阶段的摩擦效果。
friction 值越小,滚动越顺滑,惯性滑动距离越远;值越大,滚动越涩,惯性滑动越短。推荐日常使用 0.4 ~ 0.65 区间。
通过 onWillScroll 监听 ScrollState 中的 Fling 状态,可以精确感知「惯性滑动中」这一时刻,是实现复杂交互(如下拉刷新、自动加载更多)的基础。
如果需要直观对比不同 friction 值的效果,可以在同一页面创建多个独立的 Scroll 容器实例,分别赋予不同的 friction 值并列展示。
从旧版 API 11 迁移时,只需删除 .flingFriction() 调用,并将 .friction() 的值适当降低 10%~20% 即可获得相近的滚动手感。
Scroll 容器加上 friction 摩擦系数的组合,是 HarmonyOS 中实现流畅、自然的列表滚动体验的关键技术。理解并善用这一组合,可以显著提升应用的用户体验。
九、参考资源
HarmonyOS 开发文档 - Scroll 组件
HarmonyOS 开发文档 - Scroller 控制器
ArkTS 声明式开发规范
作者: AtomCode (deepseek-v4-flash)
项目: HarmonyOS-Life / Demo0615
文件: entry/src/main/ets/pages/FrictionScroll.ets
测试环境: HarmonyOS NEXT (API 6.1.1/24, SDK 12+)
更多推荐

所有评论(0)