1# 下拉刷新与上滑加载案例 2 3### 介绍 4 5本示例介绍使用第三方库的PullToRefresh组件实现列表的下拉刷新数据和上滑加载后续数据。 6 7### 效果图预览 8 9<img src="./pulltorefresh/src/main/resources/base/media/pull_to_refresh_news.gif" width="300"> 10 11**使用说明** 12 131. 进入页面,下拉列表触发刷新数据事件,等待数据刷新完成。 142. 上滑列表到底部,触发加载更多数据事件,等待数据加载完成。 15 16### 实现思路 17 181. 使用第三方库pullToRefresh组件,将列表组件、绑定的数据对象和scroller对象包含进去,并添加上滑与下拉方法。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 19 ```typescript 20 PullToRefresh({ 21 // 必传项,列表组件所绑定的数据 22 data: $newsData, 23 // 必传项,需绑定传入主体布局内的列表或宫格组件 24 scroller: this.scroller, 25 // 必传项,自定义主体布局,内部有列表或宫格组件 26 customList: () => { 27 // 一个用@Builder修饰过的UI方法 28 this.getListView(); 29 }, 30 // 下拉刷新回调 31 onRefresh: () => { 32 return new Promise<string>((resolve, reject) => { 33 ... 34 }); 35 }, 36 // 上滑加载回调 37 onLoadMore: () => { 38 return new Promise<string>((resolve, reject) => { 39 ... 40 }); 41 }, 42 customLoad: () => this.customLoad(), 43 customRefresh: null, 44 }) 45 ``` 462. 使用LazyForEach循环渲染列表。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 47 ```typescript 48 LazyForEach(this.newsData, (item: NewsData) => { 49 ListItem() { 50 ... 51 } 52 }); 53 ``` 543. 模拟列表总页数,加载完全部信息后提示已经到底部。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 55 ```javascript 56 onLoadMore: () => { 57 return new Promise<string>((resolve, reject) => { 58 // 模拟数据列表页超过4页后已到达底部,无法继续加载 59 if (this.newsDataListIndex < NEWS_MAX_LIST) { 60 // 模拟网络请求操作,请求网络1.5秒后得到数据,通知组件变更列表数据 61 setTimeout(() => { 62 let newsModelMockData: Array<NewsData> = getNews(MOCK_DATA_FILE_ONE_DIR) 63 for (let j = 0; j < NEWS_MOCK_DATA_COUNT; j++) { 64 this.newsData.pushData(newsModelMockData[j]); 65 } 66 this.newsDataListIndex++; 67 resolve(''); 68 }, NEWS_REFRESH_TIME); 69 } else { 70 // 如果已满4页,更改上拉提示信息提示已经加载完所有数据 71 setTimeout(() => { 72 resolve(''); 73 }, NEWS_REFRESH_TIME); 74 } 75 }); 76 } 77 ``` 784. 自定义下拉更新动画。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 79 ```typescript 80 @Builder 81 private customRefresh() { 82 Row() { 83 // 下滑加载图片 84 Image($r('app.media.pull_icon_load')) 85 .width($r('app.string.pull_refresh_load_width')) 86 .height($r('app.string.pull_refresh_load_height')) 87 .objectFit(ImageFit.Contain) 88 .rotate({ 89 z: 1, 90 angle: this.angle2 !== undefined ? this.angle2 : 0 91 }) 92 .width(this.refreshConfigurator.getLoadImgHeight()) 93 .height(this.refreshConfigurator.getLoadImgHeight()) 94 95 // 下拉时提示文本 96 Stack() { 97 Text(CURRENT_DATA_TIP) 98 .height($r('app.string.pull_refresh_load_height')) 99 .textAlign(TextAlign.Center) 100 .margin({ left: this.newsDataListIndex === NEWS_MAX_LIST && this.isLoading ? 0 : 8 }) 101 .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextColor() : 0) 102 .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextSize() : 0) 103 .visibility(this.pullHeightValue <= CHANGE_PAGE_STATE ? Visibility.Visible : Visibility.Hidden) 104 Text(NEW_DATA_TIP) 105 .height($r('app.string.pull_refresh_load_height')) 106 .textAlign(TextAlign.Center) 107 .margin({ left: this.newsDataListIndex === NEWS_MAX_LIST && this.isLoading ? 0 : 8 }) 108 .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextColor() : 0) 109 .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextSize() : 0) 110 .visibility(this.pullHeightValue > CHANGE_PAGE_STATE ? Visibility.Visible : Visibility.Hidden) 111 } 112 } 113 .height($r('app.string.pull_refresh_load_height')) 114 } 115 ``` 1165. 通过下拉距离判断页面是刷新当前数据还是更新为下一页。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 117 ```typescript 118 onAnimPullDown: (value) => { 119 this.pullHeightValue = value; 120 }, 121 onAnimRefreshing: (value, width, height) => { 122 if (value !== undefined && width !== undefined && height !== undefined) { 123 if (value) { 124 this.angle2 = value * 360; 125 if (this.pullHeightValue > LOAD_PULL_STATE_CHANGE && this.pullHeightValue <= CHANGE_PAGE_STATE) { 126 this.isChangePage = false; 127 } else { 128 // 当下拉到最顶部时,触发更新页面,不再刷新当前页。 129 this.isChangePage = true; 130 } 131 } 132 } 133 } 134 ``` 1356. 自定义上拉加载动画。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 136 ```typescript 137 @Builder 138 private customLoad() { 139 Row() { 140 Stack() { 141 // 上拉1阶段箭头图片 142 Image(pull_icon_up) 143 .width('100%') 144 .height('100%') 145 .objectFit(ImageFit.Contain) 146 .visibility(this.isPullUp ? Visibility.Visible : Visibility.Hidden) 147 .rotate({ 148 z: 1, 149 angle: this.angle1 !== undefined ? this.angle1 : 0 150 }) 151 // 加载时图片 152 Image(pull_icon_load) 153 .width('100%') 154 .height('100%') 155 .objectFit(ImageFit.Contain) 156 .visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden) 157 .rotate({ 158 z: 1, 159 angle: this.angle2 !== undefined ? this.angle2 : 0 160 }) 161 } 162 // 最后一页加载时隐藏加载图片 163 .width(this.newsDataListIndex === NEWS_MAX_LIST && this.isLoading ? 0 : 164 this.refreshConfigurator.getLoadImgHeight()) 165 .height(this.newsDataListIndex === NEWS_MAX_LIST && this.isLoading ? 0 : 166 this.refreshConfigurator.getLoadImgHeight()) 167 168 // 上拉过程与加载时提示文本 169 Text(this.loadText) 170 .height('100%') 171 .textAlign(TextAlign.Center) 172 .margin({ left: this.newsDataListIndex === NEWS_MAX_LIST && this.isLoading ? 0 : 8 }) 173 .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextColor() : 0) 174 .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextSize() : 0) 175 } 176 .height('100%') 177 } 178 ``` 1797. 设置上拉与加载时的动画回调。源码参考[PullToRefreshNews.ets](./pulltorefresh/src/main/ets/pages/PullToRefreshNews.ets) 180 ```typescript 181 onAnimPullUp: (value, width, height) => { 182 if (value !== undefined && width !== undefined && height !== undefined) { 183 if (value) { 184 this.isLoading = false; 185 this.isPullUp = true; 186 // 判断上拉拖拽过程中高度是否超过阶段临界值 187 if (value < LOAD_PULL_STATE_CHANGE) { 188 // 归零角度,保持箭头朝上 189 this.angle1 = 0; 190 // 改变提示文本为上拉1阶段 191 this.loadText = LOAD_TEXT_PULL_UP_1; 192 } else { 193 // 翻转角度,保持箭头朝下 194 this.angle1 = 180; 195 // 改变提示文本为上拉2阶段 196 this.loadText = LOAD_TEXT_PULL_UP_2; 197 } 198 } 199 } 200 }, 201 202 onAnimLoading: (value, width, height) => { 203 if (value !== undefined && width !== undefined && height !== undefined) { 204 if (value) { 205 this.isPullUp = false; 206 this.isLoading = true; 207 // 更改角度使加载图片保持旋转 208 this.angle2 = value * 360; 209 // 判读页码是否为最后一页 210 if (this.newsDataListIndex !== NEWS_MAX_LIST) { 211 this.loadText = LOAD_DEFAULT_TEXT; 212 } else { 213 // 最后一页更换文本提示已经到底了 214 this.loadText = LOAD_STOP_TEXT; 215 } 216 } 217 } 218 } 219 ``` 220### 高性能知识点 221 222不涉及 223 224### 工程结构&模块类型 225 ``` 226 pulltorefreshnews // har类型 227 |---model 228 |---|---AppInfo.ets // App信息 229 |---|---UserInformation.ets // 用户信息 230 |---view 231 |---|---PullToRefreshNews.ets // 视图层-场景列表页面 232 ``` 233 234### 模块依赖 235 236[PullToRefresh](https://gitee.com/openharmony-sig/PullToRefresh) 237 238### 参考资料 239 240[PullToRefresh](https://gitee.com/openharmony-sig/PullToRefresh) 241[LazyForEach](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md) 242[PullToRefresh第三方库](https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Fpulltorefresh) 243 244### 相关权限 245 246不涉及。 247 248### 依赖 249 250不涉及。 251 252### 约束与限制 253 2541.本示例仅支持标准系统上运行,支持设备:RK3586。 255 2562.本示例为Stage模型,支持API12版本SDK,SDK版本号(API Version 12 Release)。 257 2583.本示例需要使用DevEco Studio版本号(DevEco Studio 5.0.0 Release)及以上版本才可编译运行。 259 260### 下载 261 262如需单独下载本工程,执行如下命令: 263 264```shell 265git init 266git config core.sparsecheckout true 267echo code/UI/PullToRefresh/ > .git/info/sparse-checkout 268git remote add origin https://gitee.com/openharmony/applications_app_samples.git 269git pull origin master