文章目录核心概念与场景1 核心概念2 适用场景三、完整可运行代码Grid嵌套滚动核心实现1 滚动控制器Scroller配置2 嵌套滚动模式NestedScrollMode配置3 触摸事件监听4 滚动事件监听精准控制滚动行为4.1 onScrollStart滚动开始事件4.2 onScrollFrameBegin滚动帧开始事件4.3 onScrollStop滚动停止事件5 一键回到顶部功能嵌套结构解析总结嵌套滚动相关APINestedScrollMode 枚举值核心概念与场景1 核心概念父滚动容器本文中为List组件作为外层可滚动容器承载整体页面的纵向滚动如顶部标题栏、多个子组件的滚动切换子滚动容器本文中为Grid组件作为List的子项承载自身数据的纵向滚动如大量商品网格的滚动滚动冲突当触摸操作同时触发父容器与子容器的滚动响应时出现的滚动错乱、响应不精准问题需通过配置和事件监听解决NestedScrollMode嵌套滚动模式用于定义父、子容器的滚动优先级是解决滚动冲突的核心配置。2 适用场景Grid嵌套滚动适用于需要“整体滚动局部独立滚动”的场景典型案例包括电商首页顶部分类栏Grid 下方商品列表Grid整体页面可滚动商品列表可独立滚动分类页面左侧分类导航Grid 右侧内容列表Grid支持整体滚动和右侧列表独立滚动大数据量展示外层List承载多个分区每个分区内部是可独立滚动的Grid提升数据展示效率和交互体验。三、完整可运行代码// GridDataSource.etsexportclassGridDataSourceimplementsIDataSource{privatelist:string[][];privatelisteners:DataChangeListener[][];constructor(list:string[]){this.listlist;}totalCount():number{returnthis.list.length;}getData(index:number):string{returnthis.list[index];}registerDataChangeListener(listener:DataChangeListener):void{if(this.listeners.indexOf(listener)0){this.listeners.push(listener);}}unregisterDataChangeListener(listener:DataChangeListener):void{constposthis.listeners.indexOf(listener);if(pos0){this.listeners.splice(pos,1);}}// 通知控制器数据位置变化notifyDataMove(from:number,to:number):void{this.listeners.forEach(listener{listener.onDataMove(from,to);})}// 交换元素位置publicswapItem(from:number,to:number):void{lettemp:stringthis.list[from];this.list[from]this.list[to];this.list[to]temp;this.notifyDataMove(from,to);}}EntryComponentstruct GridExample{Statecolors:number[][0xFFC0CB,0xDA70D6,0x6B8E23,0x6A5ACD,0x00FFFF,0x00FF7F];numbers:GridDataSourcenewGridDataSource([]);StatetranslateY:number0;privatescroller:ScrollernewScroller();privategridScroller:ScrollernewScroller();privatetouchDown:booleanfalse;privatelistTouchDown:booleanfalse;privatescrolling:booleanfalse;aboutToAppear(){letlist:string[][];for(leti0;i100;i){list.push(i.toString());}this.numbersnewGridDataSource(list);}build(){Stack(){Column(){Row(){Text(Head)}Column(){List({scroller:this.scroller}){ListItem(){Grid(){GridItem(){Text(GoodsTypeList1)}.backgroundColor(this.colors[0]).columnStart(0).columnEnd(1)GridItem(){Text(GoodsTypeList2)}.backgroundColor(this.colors[1]).columnStart(0).columnEnd(1)GridItem(){Text(GoodsTypeList3)}.backgroundColor(this.colors[2]).columnStart(0).columnEnd(1)GridItem(){Text(GoodsTypeList4)}.backgroundColor(this.colors[3]).columnStart(0).columnEnd(1)GridItem(){Text(GoodsTypeList5)}.backgroundColor(this.colors[4]).columnStart(0).columnEnd(1)}.scrollBar(BarState.Off).columnsGap(15).rowsGap(10).rowsTemplate(1fr 1fr 1fr 1fr 1fr).columnsTemplate(1fr).width(100%).height(200)}ListItem(){Grid(this.gridScroller){LazyForEach(this.numbers,(item:string){GridItem(){Text(item).fontSize(16).backgroundColor(0xF9CF93).width(100%).height(100%).textAlign(TextAlign.Center)}.width(100%).height(40).shadow({radius:10,color:#909399,offsetX:1,offsetY:1}).borderRadius(10).translate({x:0,y:this.translateY})},(item:string)item)}.columnsTemplate(1fr 1fr).friction(0.3).columnsGap(15).rowsGap(10).scrollBar(BarState.Off).width(100%).height(100%).layoutDirection(GridDirection.Column).nestedScroll({scrollForward:NestedScrollMode.PARENT_FIRST,scrollBackward:NestedScrollMode.SELF_FIRST}).onTouch((event:TouchEvent){if(event.typeTouchType.Down){this.listTouchDowntrue;}elseif(event.typeTouchType.Up){this.listTouchDownfalse;}})}}.scrollBar(BarState.Off).edgeEffect(EdgeEffect.None).onTouch((event:TouchEvent){if(event.typeTouchType.Down){this.touchDowntrue;}elseif(event.typeTouchType.Up){this.touchDownfalse;}}).onScrollFrameBegin((offset:number,state:ScrollState){if(this.scrollingoffset0){letnewOffsetthis.scroller.currentOffset().yOffset;if(newOffset590){this.gridScroller.scrollBy(0,offset);return{offsetRemain:0};}elseif(newOffsetoffset590){this.gridScroller.scrollBy(0,newOffsetoffset-590);return{offsetRemain:590-newOffset};}}return{offsetRemain:offset};}).onScrollStart((){if(this.touchDown!this.listTouchDown){this.scrollingtrue;}}).onScrollStop((){this.scrollingfalse;})}.width(100%).height(100%).padding({left:10,right:10})}Row(){Text(Top).width(30).height(30).borderRadius(50)}.padding(5).borderRadius(50).backgroundColor(#ffffff).shadow({radius:10,color:#909399,offsetX:1,offsetY:1}).margin({right:22,bottom:15}).onClick((){this.scroller.scrollTo({xOffset:0,yOffset:0});this.gridScroller.scrollTo({xOffset:0,yOffset:0});})}.align(Alignment.BottomEnd)}}运行效果如图当滑动页面点击Top 页面又回回到顶部Grid嵌套滚动核心实现本文示例采用“List父容器→ ListItem → Grid子容器”的嵌套结构通过Scroller控制器、NestedScrollMode配置、触摸与滚动事件监听实现无冲突的嵌套滚动。以下分模块解析核心代码。1 滚动控制器Scroller配置为父容器List和子容器Grid分别绑定独立的Scroller实例实现对两个容器滚动状态的单独控制这是嵌套滚动的基础。// 父容器List滚动控制器privatescroller:ScrollernewScroller();// 子容器Grid滚动控制器privategridScroller:ScrollernewScroller();// 父容器绑定控制器List({scroller:this.scroller}){...}// 子容器绑定控制器Grid(this.gridScroller){...}作用通过Scroller可实现程序化滚动如一键回到顶部同时精准获取各自的滚动偏移量为滚动冲突处理提供数据支持。2 嵌套滚动模式NestedScrollMode配置通过Grid的nestedScroll属性配置嵌套滚动优先级这是解决滚动冲突的核心配置官方API明确该属性用于定义父、子容器的滚动响应顺序。.nestedScroll({scrollForward:NestedScrollMode.PARENT_FIRST,// 向下滚动时父容器优先响应scrollBackward:NestedScrollMode.SELF_FIRST// 向上滚动时子容器优先响应})3 触摸事件监听通过触摸事件onTouch区分“触摸父容器”和“触摸子容器”避免触摸子Grid时触发父List的滚动进一步优化滚动冲突。// 子容器Grid触摸事件标记是否触摸子容器.onTouch((event:TouchEvent){if(event.typeTouchType.Down){this.listTouchDowntrue;// 触摸子Grid标记为true}elseif(event.typeTouchType.Up){this.listTouchDownfalse;// 离开触摸标记为false}})// 父容器List触摸事件标记是否触摸父容器.onTouch((event:TouchEvent){if(event.typeTouchType.Down){this.touchDowntrue;// 触摸父List标记为true}elseif(event.typeTouchType.Up){this.touchDownfalse;// 离开触摸标记为false}})作用通过listTouchDown和touchDown两个状态变量判断当前触摸操作的目标容器为后续滚动事件过滤提供依据。4 滚动事件监听精准控制滚动行为通过List的onScrollStart、onScrollFrameBegin、onScrollStop事件实现滚动状态的精准控制避免父、子容器滚动干扰。4.1 onScrollStart滚动开始事件.onScrollStart((){if(this.touchDown!this.listTouchDown){this.scrollingtrue;// 仅当触摸父容器、未触摸子容器时标记为滚动中}})作用判断滚动触发的来源只有触摸父容器时才开启父容器的滚动控制避免触摸子容器时触发父容器滚动。4.2 onScrollFrameBegin滚动帧开始事件该事件在滚动每帧开始时触发可通过返回值控制实际滚动量是解决嵌套滚动冲突的关键事件官方文档明确其用于自定义滚动量分配。.onScrollFrameBegin((offset:number,state:ScrollState){if(this.scrollingoffset0){letnewOffsetthis.scroller.currentOffset().yOffset;// 父容器滚动到指定偏移量590vp后剩余滚动量交给子容器if(newOffset590){this.gridScroller.scrollBy(0,offset);return{offsetRemain:0};// 父容器不滚动剩余滚动量为0}elseif(newOffsetoffset590){// 父容器滚动到590vp剩余滚动量交给子容器this.gridScroller.scrollBy(0,newOffsetoffset-590);return{offsetRemain:590-newOffset};// 父容器滚动剩余量}}return{offsetRemain:offset};// 正常滚动滚动量不变})作用实现“父容器滚动到指定位置后子容器开始滚动”的逻辑避免两者同时滚动确保滚动交互的流畅性。4.3 onScrollStop滚动停止事件.onScrollStop((){this.scrollingfalse;// 滚动停止重置滚动状态标记})作用滚动停止后重置scrolling状态避免后续滚动逻辑误触发。5 一键回到顶部功能通过两个Scroller的scrollTo方法实现父、子容器同时回到顶部提升用户体验。.onClick((){// 父容器List回到顶部this.scroller.scrollTo({xOffset:0,yOffset:0});// 子容器Grid回到顶部this.gridScroller.scrollTo({xOffset:0,yOffset:0});})嵌套结构解析本文示例的嵌套结构分为3层每层职责清晰符合HarmonyOS组件布局规范具体结构如下Stack根容器 ├─ Column主布局 │ ├─ Row顶部标题栏Head │ └─ Column内容容器 │ └─ List父滚动容器 │ ├─ ListItem 1分类Grid容器 │ │ └─ Grid分类Grid不可滚动 │ │ └─ GridItem5个分类项 │ └─ ListItem 2商品Grid容器 │ └─ Grid子滚动容器 │ └─ GridItem100个商品项通过LazyForEach懒加载 └─ Row回到顶部按钮Top总结嵌套滚动相关APIAPI/属性作用关键说明nestedScroll配置嵌套滚动模式Grid的核心属性解决滚动冲突必填Scroller滚动控制器父、子容器需分别创建实例独立控制onScrollFrameBegin滚动帧开始事件自定义滚动量分配控制父、子容器滚动优先级每帧触发一次onScrollStart/onScrollStop滚动开始/停止事件标记滚动状态避免滚动逻辑误触发不支持混合页面场景onTouch触摸事件区分触摸目标容器辅助解决滚动冲突NestedScrollMode 枚举值枚举值描述PARENT_FIRST父容器优先响应滚动SELF_FIRST子容器优先响应滚动PARENT_ONLY仅父容器响应滚动子容器不响应SELF_ONLY仅子容器响应滚动父容器不响应如果这篇文章对你有帮助欢迎点赞、收藏、关注你的支持是持续创作的动力