HarmonyOS 6商城开发学习:上滑吸顶与标题栏渐变——用Stack+nestedScroll把“首页式“滚动手感做稳
购物比价/商城App最常见的首页长相最顶上一行自定义标题栏首页/logo/消息图标…往下是轮播Banner/运营位/快捷入口再往下是一条分类Tab同城推荐活动玩机Tab下面挂着商品列表。上滑时Banner先收走Tab行滚到标题栏下沿就吸住标题栏同时由透明/浅色慢慢变成实色白底。​这个效果看起来华丽但拆开只有两件事谁来承载标题栏的层级​ 滚动权在外层Scroll和内层列表之间怎么交接。华为官方购物比价实践专门点出了关键点Stack层叠 nestedScroll交接 onDidScroll驱动标题栏属性动画。下面用最小骨架图把它讲透不陷进百行源码。一、先把布局层级钉死标题栏必须浮在顶层而不是跟内容一起滚多数人第一次写会把标题栏放进Column → Banner → ...的流式里于是它天然随着内容一起上滚——后面再想让它固定就只能靠监听偏移反拉写法很快就脏了。正确姿势是一个Stack 页面根Stack页满屏 ├─ 内容层最底Scroll整页可滚内容 │ ├─ 前置区Banner / 快捷入口 / 运营横幅高度 H_pre │ ├─ TabBar行同城/推荐/活动/玩机这就是要吸顶的那条 │ └─ 内容区List / WaterFlow / TabContent区 │ └─ 标题栏层最顶靠 Stack 盖在上面 ├─ 背景rgba(255,255,255, opacity) ├─ 左侧首页/返回箭头 └─ 右侧消息/搜索图标核心就一句标题栏是 Stack 的最上子节点它不参与 Scroll 的流式排版它只是用opacity/backgroundColor/translate做视觉变化。​而 Scroll 是 Stack 的下一层子节点负责把前置区TabBar行列表正常往上滚。这样 TabBar 行滚到标题栏下沿时你不用做任何把标题栏钉住的动作——它本来就在那。你只要保证TabBar行到达顶部的那一刻外层的Scroll别再把TabBar行继续卷出屏幕也就是让TabBar行在那个位置变成逻辑上的顶。二、Scroll嵌套列表吸顶的本质是滚动权的父子交接你面对的是一个经典嵌套滚动场景外层Scroll管 Banner区 TabBar行 的位移内层List / WaterFlow / TabContent里的列表管商品流的滚动吸顶不是靠一个属性开关stickyHeader之类只是ListItem组的行为这里更通用的解法是控制谁在当前手势下有权滚动——这就是nestedScroll的两个方向手势方向参数含义口语版手指上滑内容往上走scrollForwardPARENT_FIRST外层Scroll先滚先把Banner区卷走直到TabBar行贴到标题栏下沿到了边缘后滚动权才给内层列表​手指下滑内容往下走scrollBackwardSELF_FIRST内层列表先滚先把列表拉到底视觉上是先让列表到顶到顶后滚动权还给外层让Banner区重新拉回来、TabBar行从吸顶位置落下去写成代码只写这几行骨架你不想要大段源码我给它压到最少可读量// 内层列表 / WaterFlow 的 nestedScroll .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, // 向上滑外层优先 scrollBackward: NestedScrollMode.SELF_FIRST // 向下滑内层先回顶再交外层 }) .edgeEffect(EdgeEffect.None, { alwaysEnabled: true })这里有个很容易踩的坑.edgeEffect(EdgeEffect.None)必须配alwaysEnabled:true。否则列表到顶时边缘感知会断片外层Scroll一时接不到权你看到的就是 TabBar 在顶上轻微抖一下——像弹簧咔一下。三、标题栏渐变驱动源不是TabBar到哪了而是Scroll滚了多少标题栏要变的是背景从透明→实色、文字从浅色→深色、甚至高度/阴影随滚动出现。你需要一个可靠的滚动偏移而不是去算TabBar离顶距离// 外层 Scroll State scrolledY: number 0 State titleOpacity: number 0 // 0全透, 1实底 .onDidScroll((xOff, yOff, state) { this.scrolledY this.scroller.currentOffset().yOffset // fadeEnd 你可以取前置区高度Banner区或前置区高度×0.8 做柔和区间 const fadeEnd this.bannerZoneHeight const ratio Math.max(0, Math.min(1, this.scrolledY / fadeEnd)) // 用 animateTo 包一下避免每帧硬跳造成闪烁 animateTo(0, () { this.titleOpacity ratio }) })标题栏那层的样式写成由titleOpacity驱动backgroundColor: rgba(255,255,255, titleOpacity)或blendAlpha文字颜色titleOpacity 0.5 ? #FFFFFF : #111111阴影只在titleOpacity 0.95时显示可选避免滚动中一直画shadow这样做的好处是标题栏变化跟滑动是同一帧来源(onDidScroll)不会出现Tab吸住了但标题栏晚半拍。四、Stack 安全区标题栏千万别忘了 expandSafeArea 与状态栏高度商城首页标题栏几乎必做两件事吃状态栏高度用expandSafeArea([SafeAreaType.SYSTEM], SafeAreaEdge.TOP)让标题栏内容区真正顶到屏幕上沿标题栏背景的实色层要独立于内容层因为内容层会滚你在Stack里用Column做一个标题栏壳底层Rectangle/Row当背景背景色由titleOpacity控制上层操作图标/文字不受内容滚动影响如果你不处理安全区滚到顶时标题栏文字就会顶进状态栏或者在刘海屏上偏半截。五、最小完整心智模型你照这个搭就不会乱把它当成三句话去验收Stack根标题栏 最顶层节点不参与Scroll内容 下层 ScrollScroll内容顺序前置区 → TabBar行 → 列表区TabBar行是内容的一部分不是barPosition那种Tabs内置bar滚动权内层列表用PARENT_FIRST / SELF_FIRST交接别自己写if-else抢事件满足这三条吸顶自然成立TabBar行会在它该停的位置标题栏下沿停下——因为它没被额外拉走只是Scroll把前面的兄弟卷上去而已。六、最容易踩的坑速查表现象根因修法TabBar吸到一半抖一下edgeEffect默认Spring回弹把到顶交接搞出微反冲.edgeEffect(None,{alwaysEnabled:true})标题栏文字顶进状态栏没做expandSafeArea / 没补状态栏高度 paddingTop标题栏壳加expandSafeArea([SYSTEM], TOP)并补padding({top: statusBarH})内层列表在上滑时抢滚Banner区没卷完Tab就动了nestedScroll没配或配反了确认scrollForward:PARENT_FIRST标题栏背景变化跳格在onDidScroll里直接赋值不插值用animateTo包赋值或直接用线性插值别用布尔强切吸顶在折叠屏展开/旋转后偏前置区高度写死像素没按vp/动态测量前置区高度用布局约束或componentSnapshot拿一次别写死七、总结商城首页上滑吸顶 标题栏渐变不是黑魔法Stack​ 解决层级标题栏悬浮内容滚在下面nestedScroll(PARENT_FIRST / SELF_FIRST)​ 解决滚动权Banner先走、Tab再吸、列表再接onDidScroll 归一化比值 animateTo​ 解决标题栏从透到实的连续感把这三块钉住这个首页给人的感觉就不是组件凑出来的而是像主流电商App那种顺滑、稳、不抖的手感——用户说不出来为什么舒服但他们能感觉到。下一篇可以延伸的方向把这条TabBar行的吸顶改成真·stickyHeader当你的TabBar不是简单一行文字而是带筛选下拉/胶囊组时以及onDidScroll在低帧率设备上的节流策略。

相关新闻