欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > 鸿蒙5.0实战案例:基于List和Scroller由简单到复杂列表布局开发实践

鸿蒙5.0实战案例:基于List和Scroller由简单到复杂列表布局开发实践

2025/2/24 0:19:08 来源:https://blog.csdn.net/Maniu_666/article/details/145704177  浏览:    关键词:鸿蒙5.0实战案例:基于List和Scroller由简单到复杂列表布局开发实践

往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)

✏️ 鸿蒙(HarmonyOS)北向开发知识点记录~

✏️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~

✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

✏️ 记录一场鸿蒙开发岗位面试经历~

✏️ 持续更新中……


场景描述

在多列表页面开发中,数据展示往往有联动关系,

  • 场景一:单列表布局多长列表页面,如门户首页、商城首页
  • 场景二:双列表滚动联动,如城市选择
  • 场景三:多列表滚动横向纵向联动,如汽车参数对比,股票信息列表

方案描述

场景一:

单列表布局多长列表页面,如门户首页、商城首页效果图

方案

运用List组件作为整个首页长列表的容器,通过ListItem对不同模块进行定制。

  1. Refresh包裹List实现下拉刷新
  2. ListItem-0嵌套Swiper实现轮播图。
  3. ListItem-1嵌套Grid实现快捷入口。
  4. ListItem-2嵌套Column实现秒杀
  5. ListItemGroup实现商品分类列表
  6. 最底部ListItem实现触底自动加载

核心代码

build() {Column() {// 搜索框 置顶if (this.searchSticky) {this.searchBarBuilder()}// 下拉刷新组件Refresh({ refreshing: $$this.isRefreshing }) {// List组件作为长列表布局List({ space: 10 }) {// 搜索框跟随if (!this.searchSticky) {ListItem() {this.searchBarBuilder()}}// ListItem 自定义Swiper轮播图模块ListItem() {this.bannerBuilder()}// ListItem 自定义Grid快接入口模块ListItem() {this.quickBuilder()}// ListItem 自定义Column秒杀模块ListItem() {this.flashBuilder()}// ListItemGroup 商品分类列表this.productsBuilder()// 最后ListItem 自定义触底加载更多ListItem() {this.footerLoadingBuilder()}.height(50).width('100%').backgroundColor(0xeeeeee)}.sticky(StickyStyle.Header).edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true }).height('100%').layoutWeight(2)// List组件触底模拟网络请求.onReachEnd(() => {if (this.productsArray.length >= 20) {this.noMoreData = truereturn}setTimeout(() => {this.productsArray.push('商品' + (this.productsArray.length + 1))}, 2000)})}// 下拉刷新模拟网络请求.onRefreshing(() => {setTimeout(() => {this.productsArray = ['商品1', '商品2', '商品3', '商品4', '商品5']this.noMoreData = falsethis.isRefreshing = !this.isRefreshing}, 2000)}).layoutWeight(1).width('95%')}}

场景二:

双列表滚动同向联动,如城市选择

效果图

方案

整体运用Stack组件(List组件+List组件)布局,左List作为城市列表,右List快捷导航列表,通过ListItem对对应数据进行渲染。

1.左List用ListItemGroup对城市数据进行分组

2.右List用ListItem对首字母进行渲染

3.通过右List首字母导航点击可以切换左List滚动到对应分组

核心代码

@State private selectGroupIndex: number = -1 //导航栏选中indexprivate cityScroller: ListScroller = new ListScroller() // 城市列表Scoller控制器private navgationScroller: ListScroller = new ListScroller() // 导航列表Scoller控制器private isClickScroll:boolean = false // 导航列表点击标记为true,城市列表触摸滚动为falsebuild() {Stack({alignContent : Alignment.End}) {this.cityList()this.navigationList()}.width('100%').height('100%').backgroundColor(0xFFFFFF)}// 城市列表@BuildercityList() {List({ scroller: this.cityScroller }) {ListItemGroup({ header: this.itemHead('当前城市') }) {ListItem() {Text(this.currentCity)......}}ListItemGroup({ header: this.itemHead('热门城市') }) {ForEach(this.hotCities, (hotCity: string) => {ListItem() {Text(hotCity)......}})}// A~Z城市分组ForEach(this.groupNameList, (item: string) => {ListItemGroup({ header: this.itemHead(item) }) {ForEach(this.getCitiesWithGroupName(item), (cityItem: City) => {ListItem() {Text(cityItem.city)......}}, (item: City) => item.city)}})}.width('100%').height('100%').scrollBar(BarState.Off).sticky(StickyStyle.Header).onTouch(()=>{// 城市列表触摸滚动,isClickScroll=false,防止滚动过程中与导航列表触发滚动冲突this.isClickScroll = false}).onScrollIndex((start: number, end: number, center: number)=>{// 通过selectGroupIndex状态变量与start联动控制导航列表选中状态if(!this.isClickScroll)this.selectGroupIndex = start - 2})}// 导航列表@BuildernavigationList() {List({scroller:this.cityScroller1}) {ForEach(this.groupNameList, (item: string, index: number) => {ListItem() {Text(item).......onClick(() => {// 导航列表选中isClickScroll=true,防止与城市列表滚动过程中带动导航列表状态变化this.isClickScroll = truethis.selectGroupIndex = index// 通过导航选中selectGroupIndex与Scroller控制城市列表滚动到对应位置this.cityScroller.scrollToIndex(index + 2, true, ScrollAlign.START)})}}, (item: string) => item)}.listDirection(Axis.Vertical).backgroundColor(Color.Transparent).width('10%')}

场景三:

多列表滚动横向纵向联动,如汽车参数对比,股票信息列表

效果图

方案

1.Column组件(Row组件1 + Row组件2)整体布局上下两部分,Row1代表上部分,Row2代表下部分

2.上部分Row组件1(Column组件+ List组件0),Column组件用来布局固定信息,List组件0用来渲染底部内容区域表头,与下部分List组件3+进行联动滚动,如股票参数,车型列表。

3.下部分Row组件2(List组件1 + Scroll组件(List组件2)),List组件1渲染每条信息的头部,内部用ListItemGroup进行分组渲染,竖向滚动;Scroll组件用来包裹详细内容数据List组件2,与List组件1进行竖向滚动联动;List组件2用来渲染内容数据,与List组件0进行横向滚动联动。

4.List组件2作为内容数据容器,ListItem中嵌套List组件3+横向滚动,联动List组件0进行横向滚动。

核心代码

export class ShowData {sticky?:stringsub?: string[];scrollerArray?: Scroller[] = [];}@State remainOffset: number = 0 // 内容行在横向滚动时回调的offsetprivate bottomRightScroller: Scroller = new Scroller() //下部分左侧标题List(行标题)private bottomLeftScroller: Scroller = new Scroller() // 下部分右侧内容List(内容)private topRightScroller: Scroller = new Scroller() // 上部分右侧类型List(列标题)// 整体布局build() {Column() {// 上部分this.topFixed()// 下部分Row() {this.leftList()this.rightList()Line().height('100%').width(0.5).backgroundColor('#EEEEEE').position({ x: LeftItemWidth })}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top)}.height('100%').justifyContent(FlexAlign.Start).alignItems(HorizontalAlign.Start)}// 上部分整体Row(Column + List)@BuildertopFixed() {Row() {// 上部分左侧固定信息Column() {.......}........padding(10)// 分割线Line().height(100).width(0.5).backgroundColor(0xeeeeee)// 上部分右侧车型横向滚动列表List({ scroller: this.topRightScroller/* 绑定Scroller控制器与其他控制器联动*/ }) {ForEach(this.topRightArr, (item: string, index: number) => {ListItem() {.......}}, (item: string) => item)}........onScrollFrameBegin((offset: number, state: ScrollState) => {// 关键联动,通过对象保存的Scroller控制器数组遍历保持offset同步this.dataSource.getAllData().forEach(showData => {showData.scrollerArray!.forEach(scroller => {scroller.scrollTo({ xOffset: this.topRightScroller.currentOffset().xOffset + offset, yOffset: 0 })})})return { offsetRemain: offset }})}.height(100).width('100%')}// 下部分右侧内容显示区域纵向List(ListItem(List))@BuilderrightList() {List({ initialIndex: 0, scroller: this.bottomRightScroller }) {// 通过LazyForEach加载每一行LazyForEach(this.dataSource, (item: ShowData, index: number) => {ListItemGroup({ header: this.rightStickyHeader(index) }) {ForEach(item.sub, (subItem: string, index1: number) => {// 自定义ListItem中包含横向滚动ListItemComponent({scroller: item.scrollerArray![index1],scrollCallBack: (value) => {// value为子List横向滚动onScrollFrameBegin回传offset,在手指拖动时保持联动一致// 顶部车型List跟随联动this.topRightScroller.scrollTo({ xOffset: value, yOffset: 0 })// 通过对象保存的Scroller数组跟随保持联动this.dataSource.getAllData().forEach(showData => {showData.scrollerArray!.forEach(scroller => {if (scroller != item.scrollerArray![index1]) {scroller.scrollTo({ xOffset: value, yOffset: 0 })}})})},remainOffsetCallBack: (value) => {// 滚动过程中回传保持同步的offset值this.remainOffset = value}})}, (item: string) => item)}}, (item: ShowData, index: number) => item.sticky! + index)}........onScrollFrameBegin((offset: number, state: ScrollState) => {// 内容List纵向滚动带动左侧标题List跟随滚动this.bottomLeftScroller.scrollTo({xOffset: 0,yOffset: this.bottomRightScroller.currentOffset().yOffset + offset,animation: false})return { offsetRemain: offset }}).onScroll(() => {// 内容List纵向滚动过程中,每一行中子List的Scroller滚动到remainOffset与已显示的行位置保持一致this.dataSource.getAllData().forEach(showData => {showData.scrollerArray!.forEach(scroller => {scroller.scrollTo({ xOffset: this.remainOffset, yOffset: 0 })})})}).......}@Componentstruct ItemComponent {private arr: string[] = ['1', '2', '3', '4', '5', '6', '7', '8']private dataSource = new CommonDataSource<string>()private scroller?: Scroller = undefined // 内容行List绑定Scrollerprivate scrollCallBack?: (param: number) => void // 触摸滚动过程中回调实时offsetprivate remainOffsetCallBack?: (param: number) => void // 滚动时回调同步offsetaboutToAppear(): void {this.dataSource.setData(this.arr)}// 下部分参数列表每行数据List@BuilderRightSingleLineList() {List({ scroller: this.scroller }) {LazyForEach(this.dataSource, (item: string, index: number) => {ListItem() {......}.width(RightItemWidth)}, (item: string) => item)}.......onScroll(() => {// 通过callBack回调行在横向滚动时,Scroller当前的offsetif (this.remainOffsetCallBack)this.remainOffsetCallBack(this.scroller!.currentOffset().xOffset)}).onScrollFrameBegin((offset: number, state: ScrollState) => {// 触摸滚动实时跟随回调if (this.scrollCallBack) {this.scrollCallBack(this.scroller!.currentOffset().xOffset + offset)}return { offsetRemain: offset }})}build() {Column() {this.RightSingleLineList()Line().width("100%").height(0.5).backgroundColor(0xeeeeee)}.height(ItemHeight)}}

其他常见问题

1,滑动卡顿

LazyForEach数据懒加载:数据量大的List尽量用LazyForEach加载数据,可明显优化性能,经过测试列数为100以上,LazyForEach也无明显卡顿。

2,错位分析

查看左右List行高是否一致,ListItemGroup高度是否一致;onScrollFrameBegin联动回调中是否跟随保持一致。

3,嵌套滚动

如需要外层附加其他滚动,可运用嵌套属性.nestedScroll进行联动。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词