一、背景
目前货拉拉作为首批和鸿蒙合作适配的厂商 之一,已经在内部开始适配鸿蒙版货拉拉用户端
在鸿蒙开发适配过程中发现,项目中存有列表+分组的场景,按目前已有实现方式存在如下问题:
- 官方文档上推荐实现的分组列表是使用ListItemGroup的方式来实现分组
- ListItemGroup适用于静态分组,例如已经获取了全部数据之后通讯录或者城市列表分组显示
不太适用于
- 需要动态加载更多数据之后给数据动态分组
- 需要实时监听item滑动位置的上拉加载更多的场景
因为ListItemGroup被当做一个整体的item,难以实时监听到内部item的滑动位置,所以难以判断需要上拉加载更多
本文的PullToRefresh组件在开源的下拉刷新组件的基础上同时实现下拉刷新、上拉加载更多、列表动态分组功能
二、简介
PullToRefreshFor是鸿蒙下可同时实现动态分组列表进行下拉刷新、上拉加载的组件
在以下版本验证通过:
- DevEco Studio: 4.1 Canary(4.1.3.500), SDK: API11 (4.1.0)
理论上也支持API 9、10的版本
三、功能特性
- 特性1:支持下拉刷新和上拉加载更多数据
- 特性2:同时支持动态分组列表
- 监听手势事件的方式不同:PullToRefresh 使用
parallelGesture
方法获取触摸手势事件,本组件使用onTouch
方法获取手势 - 灵活度不同:PullToRefresh把整个组件进行一个大的封装,由外部传入 List 组件和数据请求函数即可,优点是使用上手简单,缺点是不太容易定制。本组件则是把下拉刷新、上拉加载、Head 作为单独的组件供外部使用,优点是可自由定制如实现本次分组列表,缺点是需要多处声明
四、安装指南
1 | bash复制代码ohpm install @huolala/pull-refresh |
五、效果示例
六、代码示例
1、头部刷新部分及头部刷新逻辑
头部下拉刷新UI视图组件为CustomRefreshLoadLayout
,当需要下拉刷新时,传入PullRefreshModel里的refreshLayoutConfig,然后添加此组件即可预设刷新 UI
通过@state 注解的 PullRefreshModel
类,当满足相应条件时,自动更新是否可见、刷新时的图片资源、刷新时的文案,控件高度
如当外部更改为可见时则使用预设控件高度显示,否则高度置为 0,则隐藏了刷新控件
1 | scss复制代码 // 下拉刷新 |
触发下拉刷新的方式,则是通过监听控件的 onTouch
方法,传入 TouchEvent 触摸数据到组件内部,通过判断下滑偏移量来更新下拉刷新组件的PullRefreshModel
类的属性值,最后通过数据更新 UI 到上面的CustomRefreshLoadLayout
中
1 | ini复制代码export function touchMovePullRefresh(dataModel: PullRefreshModel, event: TouchEvent) { |
当松开手指后,根据此前下拉滑动时记录的已满足下拉刷新的标记isCanRefresh
,满足则回调请求数据,即完成一次下拉刷新
1 | scss复制代码export function touchUpPullRefresh(dataModel: PullRefreshModel, getDataCallBack: (isLoadMore: boolean) => void) { |
2、占位head及列表head部分及交互逻辑
由于使用 ListItemGroup 会无法监听到 ListItemGroup 内部的 Item,但业务场景仍然需要分组的 UI,所以这里使用单独的占位 head 去作为分组标题的来显示
占位 head 总共有两处,一处是在 List 列表布局外面,一个是 List列表首条 Item 里
这两条 head 的用处分别是,第一条 head 用于在滑动的时候,始终悬浮在最顶部,并且通过onScrollIndex
方法获取到当前首条 Item,数据来动态更新占位 head 的数据
1 | scss复制代码Row() { |
3、列表动态分组实现逻辑
动态分组是指在获取到数据之后才能去实现分组,而不是像通讯录那种可以一次获取所有列表数据和分组数据
如果是后端给的数据已经实现分组,则可以直接按照给的分组进行 UI 渲染,然后直接进行下一页获取即可。但如果是后端给的数据里没有包含任何分组数据,则需要由我们来进行动态分组和更新数据来渲染 UI
具体做法是构建一个用来展示的 model 类的数据集合,在拿到原始数据的时候,判断每一条 head 的数据和之前记录的 head 数据是否相符,如果不符,则手动插入一条 head 数据,这条数据仅用来显示分组的标题,如果相同则继续添加原来的数据进去新的集合,只是这是一条普通的 Item 数据,最后取新的集合展示数据
1 | ini复制代码let currentHead: string = "" |
4、底部加载更多部分及加载更多逻辑
底部上拉加载视图为CustomRefreshLoadLayout
,和下拉刷新一样,复用同样的一个UI组件,只是传入的数据不一样
与下拉刷新不同的是,必须是有下一页数据时才会显示这个组件,是否有下一页数据,则在每次请求完数据的时候根据条数确定,否则显示没有更多数据的组件
1 | scss复制代码/** |
实现上拉加载更多逻辑,需要先获取是否当前已经滑动到当前页的最后一条数据了,获取的方法是通过.onScrollIndex
里当前滚动数据的角标,如果最后一条数据角标大于当前该页全部的数据的大小,则表示已经滑到该页最后一条数据。然后继续判断是否已经达到上拉触发的滑动阈值,达到就修改标记为已触发上拉加载更多
1 | matlab复制代码export function touchMoveLoadMore ( dataModel: PullRefreshModel, event: TouchEvent ) { if (dataModel. endIndex >= dataModel. dataSize - 1 ) { dataModel. offsetY = event. touches [ 0 ]. y - dataModel. downY ; if ( Math . abs (dataModel. offsetY ) > vp2px (dataModel. pullUpLoadHeight ) / 2 ) { dataModel. isCanLoadMore = true ; dataModel. loadMoreLayoutConfig . isVisible = true ; dataModel. offsetY = - vp2px (dataModel. pullUpLoadHeight ) + dataModel. offsetY * Const . Y_OFF_SET_COEFFICIENT ; } } } |
获取到上面的标记之后,则在手指松开之后,会调用获取下一页的数据,这样就完成了上拉加载更多
1 | lua复制代码export function touchUpLoadMore ( dataModel: PullRefreshModel, getDataCallBack: (isLoadMore: boolean ) => void ) { let self : PullRefreshModel = dataModel; animateTo ({ duration : Const . ANIMATION_DURATION , }, () => { self. offsetY = 0 ; }) // isCanLoadMore 为 true 表示当前已经到第一页最后一条数据并且手势上滑到了阈值 // hasMore 为 true 表示数据还有下一页,默认是 true if ((self. isCanLoadMore === true ) && (self. hasMore === true )) { self. isLoading = true ; getDataCallBack ( true ) } else { closeLoadMore (self); } } |
5、整个列表的逻辑部分
1 | scss复制代码@State data: GroupData[] = []; |
七、原理说明
通过分别构造滑动时假 head 头和未滑动时的 head 头,第一个 head 头在滑动后,通过监听onScrollIndex首条出现的ListItem 的角标动态设置数据,并且该控件处在 UI在 List 控件之上,达到悬停的效果
第二个 head 头与第一个 head 头互斥出现,滑动后即消失,在视觉上就像是通讯录分组一样的效果
八、类接口说明
- RefreshLayout:下拉刷新的UI控件,可定制
- itemHead:分组 head 头
- LoadMoreLayout:上拉加载更多 UI 空间,可定制
- NoMoreLayout:没有更多 UI 空间,可定制
- PullRefreshModel:用于控制下拉刷新和上拉加载状态记录的 model 类
属性 | 类型 | 释义 | 默认值 |
---|---|---|---|
dataSize | number | 数据大小 | 0 |
currentPage | number | 当前页码 | 1 |
pageSize | number | 每页大小 | 20 |
pullDownRefreshHeight | number | 下拉刷新组件的高度 | 70 |
pullUpLoadText | Resource | 上拉加载时的文案 | 加载中.. |
offsetY | number | Y 轴偏移值 | 0 |
pageState | number | 当前刷新组件状态,如加载中,加载完成 | loading 状态 |
startIndex | number | 列表的第一条角标值 | 0 |
endIndex | number | 列表的最后一条角标值 | 0 |
downY | number | 按下屏幕时的 Y 坐标 | 0 |
lastMoveY | number | 移动手指时最新的 Y 坐标 | 0 |
isRefreshing | boolean | 当前是否正在下拉刷新中 | false |
isCanRefresh | boolean | 是否已经满足松开手指触发刷新 | fasle |
isPullRefreshOperation | boolean | 当前正在下拉操作 | false |
isLoading | boolean | 是否正在上拉加载更多数据中 | false |
hasMore | boolean | 是否有下一页 | false |
isCanLoadMore | boolean | 是否可以加载下一页 | false |
refreshLayoutConfig | CustomRefreshLoadLayoutConfig | 下拉刷新组件内部使用的属性值 | - |
loadMoreLayoutConfig | CustomRefreshLoadLayoutConfig | 上拉加载组件内部使用的数值 | - |
九、开源地址
本文转载自: 掘金