当前位置:   article > 正文

【HarmonyOS开发】ArkUI实现下拉刷新/上拉加载

【HarmonyOS开发】ArkUI实现下拉刷新/上拉加载

 列表下拉刷新、上拉加载更多,不管在web时代还是鸿蒙应用都是一个非常常用的功能,基于ArkUI中TS扩展的声明式开发范式实现一个下拉刷新,上拉加载。

上拉加载、下拉刷新

如果数据量过大,可以使用LazyForEach代替ForEach

高阶组件-上拉加载,下拉刷新icon-default.png?t=N7T8https://gitee.com/bingtengaoyu/harmonyos-advanced-componen/tree/master/UpDownRefresh 

1、涉及的知识点

2、效果图

3、实现思路

根据触摸事件onTouch()处理下拉和上拉,通过记录手指按下的y坐标和move移动的距离判断属于刷拉还是下滑,从而展示不同的内容。

4、关键代码

4.1 生成下拉刷新/上拉加载DOM

  1. @Builder UpDownRefreshBuilder(text: string, state: string) {
  2. Row() {
  3. Image($r('app.media.refreshing'))
  4. .width(32)
  5. .height(32)
  6. Text(text).fontSize(16)
  7. }
  8. .justifyContent(FlexAlign.Center)
  9. .position({
  10. y: state === 'down' ? 20 : -20
  11. })
  12. .zIndex(999)
  13. .width('94%')
  14. .height('8%')
  15. }

4.2 onTouch处理事件

  1. private currentOffsetY: number = 0;
  2. private timer: number = 0;
  3. @State refreshStatus: boolean = false;
  4. @State upRefreshStatus: boolean = false;
  5. aboutToDisappear() {
  6. this.timer = null
  7. }
  8. putDownRefresh(event?: TouchEvent): void {
  9. if (event === undefined) {
  10. return;
  11. }
  12. switch (event.type) {
  13. case TouchType.Down:
  14. this.currentOffsetY = event.touches[0].y;
  15. break;
  16. case TouchType.Move:
  17. if(this.scroller.currentOffset().yOffset < 50) {
  18. this.refreshStatus = event.touches[0].y - this.currentOffsetY > 50;
  19. }
  20. this.upRefreshStatus = event.touches[0].y - this.currentOffsetY < -50;
  21. break;
  22. case TouchType.Cancel:
  23. break;
  24. case TouchType.Up:
  25. // Only simulation effect, no data request
  26. this.timer = setTimeout(() => {
  27. if (this.upRefreshStatus) {
  28. this.scroller.scrollTo({ // 调用scrollTo滚动到具体位置
  29. xOffset: 0, // 竖直方向滚动,该值不起作用
  30. yOffset: 680, // 滚动到底部
  31. animation: { // 滚动动画
  32. duration: 1500,
  33. curve: Curve.EaseOut
  34. }
  35. })
  36. }
  37. this.refreshStatus = false;
  38. this.upRefreshStatus = false;
  39. }, 1500);
  40. break;
  41. }
  42. }

5、完整代码

兵腾傲宇/harmonyos-healthy-live - Gitee.com

  1. import router from '@ohos.router'
  2. import curves from '@ohos.curves'
  3. import { BreakpointSystem, BreakPointType } from '../common/BreakpointSystem'
  4. import { FoodInfo, Category } from '../model/DataModels'
  5. import { getFoods, getFoodCategories, getSortedFoodData } from '../model/DataUtil'
  6. import { Records } from './components/DietRecord'
  7. import { PersonalCenter } from './PersonalCenter'
  8. interface FoodId {
  9. foodId: FoodInfo;
  10. }
  11. @Component
  12. struct FoodListItem {
  13. private foodItem?: FoodInfo
  14. build() {
  15. Navigator({ target: 'pages/FoodDetail' }) {
  16. Row() {
  17. Image(this.foodItem!.image!)
  18. .objectFit(ImageFit.Contain)
  19. .autoResize(false)
  20. .height(40)
  21. .width(40)
  22. .backgroundColor('#FFf1f3f5')
  23. .margin({ right: 16 })
  24. .borderRadius(6)
  25. .sharedTransition(this.foodItem!.letter, {
  26. duration: 400,
  27. curve: curves.cubicBezier(0.2, 0.2, 0.1, 1.0),
  28. delay: 100
  29. })
  30. Text(this.foodItem?.name)
  31. .fontSize(14)
  32. Blank()
  33. Text($r('app.string.calorie_with_kcal_unit', this.foodItem?.calories.toString()))
  34. .fontSize(14)
  35. }
  36. .height(64)
  37. .width('100%')
  38. }
  39. .params({ foodId: this.foodItem } as FoodId)
  40. .margin({ right: 24, left: 32 })
  41. }
  42. }
  43. @Component
  44. struct ListModeFoods {
  45. private foodItems: Array<FoodInfo | string> = getSortedFoodData()
  46. private currentOffsetY: number = 0;
  47. private timer: number = 0;
  48. @State refreshStatus: boolean = false;
  49. @State upRefreshStatus: boolean = false;
  50. aboutToDisappear() {
  51. this.timer = null
  52. }
  53. putDownRefresh(event?: TouchEvent): void {
  54. if (event === undefined) {
  55. return;
  56. }
  57. switch (event.type) {
  58. case TouchType.Down:
  59. this.currentOffsetY = event.touches[0].y;
  60. break;
  61. case TouchType.Move:
  62. if(this.scroller.currentOffset().yOffset < 50) {
  63. this.refreshStatus = event.touches[0].y - this.currentOffsetY > 50;
  64. }
  65. this.upRefreshStatus = event.touches[0].y - this.currentOffsetY < -50;
  66. break;
  67. case TouchType.Cancel:
  68. break;
  69. case TouchType.Up:
  70. // Only simulation effect, no data request
  71. this.timer = setTimeout(() => {
  72. if (this.upRefreshStatus) {
  73. this.scroller.scrollTo({ // 调用scrollTo滚动到具体位置
  74. xOffset: 0, // 竖直方向滚动,该值不起作用
  75. yOffset: 680, // 滚动到底部
  76. animation: { // 滚动动画
  77. duration: 1500,
  78. curve: Curve.EaseOut
  79. }
  80. })
  81. }
  82. this.refreshStatus = false;
  83. this.upRefreshStatus = false;
  84. }, 1500);
  85. break;
  86. }
  87. }
  88. @Builder DownRefreshBuilder(text: string, state: string) {
  89. Row() {
  90. Image($r('app.media.refreshing'))
  91. .width(32)
  92. .height(32)
  93. Text(text).fontSize(16)
  94. }
  95. .justifyContent(FlexAlign.Center)
  96. .position({
  97. y: state === 'down' ? 20 : -20
  98. })
  99. .zIndex(999)
  100. .width('94%')
  101. .height('8%')
  102. }
  103. private scroller: Scroller = new Scroller(); // 创建一个滚动控制器
  104. build() {
  105. Column() {
  106. Text($r("app.string.title_food_list"))
  107. .width('100%')
  108. .height(56)
  109. .padding({ left: 20 })
  110. .backgroundColor('#FF1f3f5')
  111. .fontSize(20)
  112. Scroll(this.scroller) {
  113. if(this.refreshStatus) {
  114. this.DownRefreshBuilder('正在刷新', 'down')
  115. }
  116. List() {
  117. ForEach(this.foodItems, (item: FoodInfo) => {
  118. ListItem() {
  119. if (item.letter !== undefined) {
  120. FoodListItem({ foodItem: item })
  121. } else {
  122. if (typeof (item) === 'string') {
  123. Text(item)
  124. .fontSize(14)
  125. .height(48)
  126. .margin({ left: 24 })
  127. .width('100%')
  128. }
  129. }
  130. }
  131. })
  132. if(this.upRefreshStatus) {
  133. ListItem(){
  134. this.DownRefreshBuilder('正在加载', 'up')
  135. }
  136. }
  137. }
  138. .layoutWeight(1)
  139. }
  140. .scrollBar(BarState.Off)
  141. .edgeEffect(EdgeEffect.Spring)
  142. .width('100%')
  143. .height('90%')
  144. .onTouch((event?: TouchEvent) => {
  145. this.putDownRefresh(event);
  146. })
  147. }
  148. }
  149. }
  150. @Component
  151. struct FoodGridItem {
  152. private foodItem?: FoodInfo
  153. build() {
  154. Column() {
  155. Image(this.foodItem!.image!)
  156. .objectFit(ImageFit.Contain)
  157. .backgroundColor('#f1f3f5')
  158. .width('100%')
  159. .height(152)
  160. .sharedTransition(this.foodItem!.letter, {
  161. duration: 400,
  162. curve: curves.cubicBezier(0.2, 0.2, 0.1, 1.0),
  163. delay: 100
  164. })
  165. Row() {
  166. Text(this.foodItem?.name)
  167. .fontSize(14)
  168. Blank()
  169. Text($r('app.string.calorie_with_kcal_unit', this.foodItem?.calories.toString()))
  170. .fontSize(14)
  171. .fontColor(0x99000000)
  172. }
  173. .padding({ left: 12, right: 12 })
  174. .width('100%')
  175. .height(32)
  176. .backgroundColor('#E5E5E5')
  177. }
  178. .height(184)
  179. .clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
  180. .onClick(() => {
  181. router.pushUrl({ url: 'pages/FoodDetail', params: { foodId: this.foodItem } })
  182. })
  183. }
  184. }
  185. @Component
  186. struct FoodGrid {
  187. @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
  188. private foodItems?: FoodInfo[]
  189. build() {
  190. Grid() {
  191. ForEach(this.foodItems!, (item: FoodInfo) => {
  192. GridItem() {
  193. FoodGridItem({ foodItem: item })
  194. }
  195. })
  196. }
  197. .columnsTemplate(new BreakPointType({
  198. sm: '1fr 1fr',
  199. md: '1fr 1fr 1fr',
  200. lg: '1fr 1fr 1fr 1fr'
  201. }).getValue(this.currentBreakpoint) as string)
  202. .columnsGap(8)
  203. .rowsGap(8)
  204. .padding({ left: 16, right: 16 })
  205. }
  206. }
  207. @Component
  208. struct CategoryModeFoods {
  209. @State currentTabIndex: number = 0
  210. private foodItems: FoodInfo[] = getFoods()
  211. private foodCategories: Category[] = getFoodCategories()
  212. /* *
  213. * 头部分类导航栏
  214. *
  215. * */
  216. @Builder
  217. tabBarItemBuilder(value: Resource, index: number) {
  218. Text(value)
  219. .fontColor(this.currentTabIndex === index ? Color.Blue : 'rgba(0,0,0,0.6)')
  220. .fontSize(this.currentTabIndex === index ? 20 : 18)
  221. .fontWeight(this.currentTabIndex === index ? FontWeight.Bold : FontWeight.Normal)
  222. .margin({ top: 2 })
  223. .height(56)
  224. }
  225. build() {
  226. Tabs() {
  227. TabContent() {
  228. FoodGrid({ foodItems: this.foodItems })
  229. }.tabBar(this.tabBarItemBuilder($r('app.string.category_all'), 0))
  230. ForEach(this.foodCategories, (foodCategory: Category, index?: number) => {
  231. TabContent() {
  232. FoodGrid({ foodItems: this.foodItems.filter(item => (item.categoryId === foodCategory.id)) })
  233. }.tabBar(this.tabBarItemBuilder(foodCategory.name!,
  234. index! + 1))
  235. })
  236. }
  237. .animationDuration(0)
  238. .barWidth('80%')
  239. .onChange((index) => {
  240. this.currentTabIndex = index
  241. })
  242. }
  243. }
  244. @Component
  245. struct FoodsDisplay {
  246. @State isCategoryMode: boolean = true
  247. @State isMoreIconOnClick: boolean = false
  248. @State isMoreIconOnHover: boolean = false
  249. @State isMoreIconOnFocus: boolean = false
  250. getMoreIconBgColor() {
  251. if (this.isMoreIconOnClick) {
  252. return $r('sys.color.ohos_id_color_click_effect')
  253. } else if (this.isMoreIconOnHover) {
  254. return $r('sys.color.ohos_id_color_hover')
  255. } else {
  256. return this.isCategoryMode ? Color.White : '#F1F3F5' || Color.Transparent
  257. }
  258. }
  259. build() {
  260. Stack({ alignContent: Alignment.TopEnd }) {
  261. if (this.isCategoryMode) {
  262. CategoryModeFoods()
  263. } else {
  264. ListModeFoods()
  265. }
  266. Row() {
  267. Image($r("app.media.ic_switch"))
  268. .height(24)
  269. .width(24)
  270. .margin({ left: 24, right: 24 })
  271. .focusable(true)
  272. }
  273. .height(56)
  274. .backgroundColor(this.getMoreIconBgColor())
  275. .stateStyles({
  276. focused: {
  277. .border({
  278. radius: $r('sys.float.ohos_id_corner_radius_clicked'),
  279. color: $r('sys.color.ohos_id_color_focused_outline'),
  280. style: BorderStyle.Solid
  281. })
  282. },
  283. normal: {
  284. .border({
  285. radius: $r('sys.float.ohos_id_corner_radius_clicked'),
  286. width: 0
  287. })
  288. }
  289. })
  290. .onFocus(() => this.isMoreIconOnFocus = true)
  291. .onBlur(() => this.isMoreIconOnFocus = false)
  292. .onHover((isOn) => this.isMoreIconOnHover = isOn)
  293. .onClick(() => {
  294. this.isCategoryMode = !this.isCategoryMode
  295. })
  296. }
  297. }
  298. }
  299. @Entry
  300. @Component
  301. struct Home {
  302. @State currentTabIndex: number = 0
  303. @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
  304. private breakpointSystem: BreakpointSystem = new BreakpointSystem()
  305. /* *
  306. * 主页和记录的tabs
  307. * */
  308. @Builder
  309. bottomBarItemBuilder(name: Resource, icon: Resource, index: number) {
  310. Flex({
  311. direction: new BreakPointType({
  312. sm: FlexDirection.Column,
  313. md: FlexDirection.Row,
  314. lg: FlexDirection.Column
  315. }).getValue(this.currentBreakpoint),
  316. justifyContent: FlexAlign.Center,
  317. alignItems: ItemAlign.Center
  318. }) {
  319. Image(icon)
  320. .height(24)
  321. .width(24)
  322. .fillColor(this.getTabBarColor(index))
  323. Text(name)
  324. .margin(new BreakPointType<Padding>({
  325. sm: { top: 4 },
  326. md: { left: 8 },
  327. lg: { top: 4 }
  328. }).getValue(this.currentBreakpoint) as Padding)
  329. .fontSize(11)
  330. .fontColor(this.getTabBarColor(index))
  331. }
  332. }
  333. aboutToAppear() {
  334. this.breakpointSystem.register()
  335. }
  336. aboutToDisappear() {
  337. this.breakpointSystem.unregister()
  338. }
  339. build() {
  340. Tabs({
  341. barPosition: new BreakPointType({
  342. sm: BarPosition.End,
  343. md: BarPosition.End,
  344. lg: BarPosition.Start
  345. }).getValue(this.currentBreakpoint)
  346. }) {
  347. TabContent() {
  348. FoodsDisplay()
  349. }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_home"), $r("app.media.ic_bottom_home"), 0))
  350. TabContent() {
  351. Records()
  352. }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_record"), $r("app.media.ic_bottom_record"), 1))
  353. TabContent() {
  354. PersonalCenter()
  355. }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_me"), $r("app.media.ic_public_me"), 2))
  356. }
  357. .vertical(new BreakPointType({ sm: false, md: false, lg: true }).getValue(this.currentBreakpoint) as boolean)
  358. .barWidth(new BreakPointType({ sm: '100%', md: '100%', lg: '56vp' }).getValue(this.currentBreakpoint) as string)
  359. .barHeight(new BreakPointType({ sm: '56vp', md: '56vp', lg: '60%' }).getValue(this.currentBreakpoint) as string)
  360. .animationDuration(300)
  361. .onChange((index) => {
  362. this.currentTabIndex = index
  363. })
  364. }
  365. private getTabBarColor(index: number) {
  366. return this.currentTabIndex == index ? $r('app.color.tab_bar_select_color') : $r('app.color.tab_bar_normal_color')
  367. }
  368. }

6、另外一个思路实现上拉加载,下拉刷新

根据List中的回调方法onScrollIndex()监听当前列表首尾索引,根据触摸事件onTouch()处理下拉和上拉。

  1. const TopHeight = 200;
  2. @Entry
  3. @Component
  4. struct Index {
  5. @State list: Array<number> = []
  6. // 列表y坐标偏移量
  7. @State offsetY: number = 0
  8. // 按下的y坐标
  9. private downY = 0
  10. // 上一次移动的y坐标
  11. private lastMoveY = 0
  12. // 当前列表首部的索引
  13. private startIndex = 0
  14. // 当前列表尾部的索引
  15. private endIndex = 0
  16. // 下拉刷新的布局高度
  17. private pullRefreshHeight = 70
  18. // 下拉刷新文字:下拉刷新、松开刷新、正在刷新、刷新成功
  19. @State pullRefreshText: string= '下拉刷新'
  20. // 下拉刷新图标:与文字对应
  21. @State pullRefreshImage: Resource = $r("app.media.ic_pull_refresh_down")
  22. // 是否可以刷新:未达到刷新条件,收缩回去
  23. private isCanRefresh = false
  24. // 是否正在刷新:刷新中不进入触摸逻辑
  25. private isRefreshing: boolean = false
  26. // 是否已经进入了下拉刷新操作
  27. private isPullRefreshOperation = false
  28. // 上拉加载的布局默认高度
  29. private loadMoreHeight = 70
  30. // 上拉加载的布局是否显示
  31. @State isVisibleLoadMore: boolean = false
  32. // 是否可以加载更多
  33. private isCanLoadMore = false
  34. // 是否加载中:加载中不进入触摸逻辑
  35. private isLoading: boolean = false
  36. // 自定义下拉刷新布局
  37. @Builder CustomPullRefreshLayout(){
  38. Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  39. Image(this.pullRefreshImage)
  40. .width(18)
  41. .height(18)
  42. Text(this.pullRefreshText)
  43. .margin({ left: 7, bottom: 1 })
  44. .fontSize(17)
  45. }
  46. .width('100%')
  47. .height(this.pullRefreshHeight)
  48. // 布局跟着列表偏移量移动
  49. .offset({ x: 0, y: `${vp2px(-this.pullRefreshHeight) + this.offsetY}px` })
  50. }
  51. // 自定义加载更多布局
  52. @Builder CustomLoadMoreLayout(){
  53. Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
  54. Image($r("app.media.ic_loading"))
  55. .width(18)
  56. .height(18)
  57. Text('加载更多中...')
  58. .margin({ left: 7, bottom: 1 })
  59. .fontSize(17)
  60. }
  61. .width('100%')
  62. .height(this.loadMoreHeight)
  63. .backgroundColor('#f4f4f4')
  64. .visibility(this.isVisibleLoadMore ? Visibility.Visible : Visibility.None)
  65. }
  66. // 刷新测试数据
  67. private refreshData(){
  68. this.list = []
  69. for (var i = 0; i < 15; i++) {
  70. this.list.push(i)
  71. }
  72. }
  73. // 加载更多测试数据
  74. private loadMoreData(){
  75. let initValue = this.list[this.list.length-1] + 1
  76. for (var i = initValue; i < initValue + 10; i++) {
  77. this.list.push(i)
  78. }
  79. }
  80. build() {
  81. Column() {
  82. // 下拉刷新布局
  83. this.CustomPullRefreshLayout()
  84. // 列表布局
  85. List() {
  86. ForEach(this.list, item => {
  87. ListItem() {
  88. Column() {
  89. Text(`Item ${item}`)
  90. .padding(15)
  91. .fontSize(18)
  92. }
  93. }
  94. }, item => item.toString())
  95. // 加载更多布局
  96. ListItem(){
  97. this.CustomLoadMoreLayout()
  98. }
  99. }
  100. .backgroundColor(Color.White) // 背景
  101. .divider({ color: '#e2e2e2', strokeWidth: 1 }) // 分割线
  102. .edgeEffect(EdgeEffect.None) // 去掉回弹效果
  103. .offset({ x: 0, y: `${this.offsetY - TopHeight}px` }) // touch事件计算的偏移量单位是px,记得加上单位
  104. .onScrollIndex((start, end) => { // 监听当前列表首位索引
  105. console.info(`${start}=start============end=${end}`)
  106. this.startIndex = start
  107. this.endIndex = end
  108. })
  109. }
  110. .width('100%')
  111. .height('100%')
  112. .backgroundColor('#f4f4f4')
  113. .onTouch((event) => this.listTouchEvent(event))// 父容器设置touch事件,当列表无数据也可以下拉刷新
  114. .onAppear(() => {
  115. this.refreshData()
  116. })
  117. }
  118. // 触摸事件
  119. listTouchEvent(event: TouchEvent){
  120. switch (event.type) {
  121. case TouchType.Down: // 手指按下
  122. // 记录按下的y坐标
  123. this.downY = event.touches[0].y
  124. this.lastMoveY = event.touches[0].y
  125. break
  126. case TouchType.Move: // 手指移动
  127. // 下拉刷新中 或 加载更多中,不进入处理逻辑
  128. if(this.isRefreshing || this.isLoading){
  129. console.info('========Move刷新中,返回=========')
  130. return
  131. }
  132. // 判断手势
  133. let isDownPull = event.touches[0].y - this.lastMoveY > 0
  134. // 下拉手势 或 已经进入了下拉刷新操作
  135. if ((isDownPull || this.isPullRefreshOperation) && !this.isCanLoadMore) {
  136. this.touchMovePullRefresh(event)
  137. } else {
  138. this.touchMoveLoadMore(event)
  139. }
  140. this.lastMoveY = event.touches[0].y
  141. break
  142. case TouchType.Up: // 手指抬起
  143. case TouchType.Cancel: // 触摸意外中断:来电界面
  144. // 刷新中 或 加载更多中,不进入处理逻辑
  145. if(this.isRefreshing || this.isLoading){
  146. console.info('========Up刷新中,返回=========')
  147. return
  148. }
  149. if (this.isPullRefreshOperation) {
  150. this.touchUpPullRefresh()
  151. } else {
  152. this.touchUpLoadMore()
  153. }
  154. break
  155. }
  156. }
  157. //============================================下拉刷新==================================================
  158. // 手指移动,处理下拉刷新
  159. touchMovePullRefresh(event:TouchEvent){
  160. // 当首部索引位于0
  161. if (this.startIndex == 0) {
  162. this.isPullRefreshOperation = true
  163. // 下拉刷新布局高度
  164. var height = vp2px(this.pullRefreshHeight)
  165. // 滑动的偏移量
  166. this.offsetY = event.touches[0].y - this.downY
  167. // 偏移量大于下拉刷新布局高度,达到刷新条件
  168. if (this.offsetY >= height) {
  169. // 状态1:松开刷新
  170. this.pullRefreshState(1)
  171. // 偏移量的值缓慢增加
  172. this.offsetY = height + this.offsetY * 0.15
  173. } else {
  174. // 状态0:下拉刷新
  175. this.pullRefreshState(0)
  176. }
  177. if (this.offsetY < 0) {
  178. this.offsetY = 0
  179. this.isPullRefreshOperation = false
  180. }
  181. }
  182. }
  183. // 手指抬起,处理下拉刷新
  184. touchUpPullRefresh(){
  185. // 是否可以刷新
  186. if (this.isCanRefresh) {
  187. console.info('======执行下拉刷新========')
  188. // 偏移量为下拉刷新布局高度
  189. this.offsetY = vp2px(this.pullRefreshHeight)
  190. // 状态2:正在刷新
  191. this.pullRefreshState(2)
  192. // 模拟耗时操作
  193. setTimeout(() => {
  194. this.refreshData()
  195. this.closeRefresh()
  196. }, 2000)
  197. } else {
  198. console.info('======关闭下拉刷新!未达到条件========')
  199. // 关闭刷新
  200. this.closeRefresh()
  201. }
  202. }
  203. // 下拉刷新状态
  204. // 0下拉刷新、1松开刷新、2正在刷新、3刷新成功
  205. pullRefreshState(state:number){
  206. switch (state) {
  207. case 0:
  208. // 初始状态
  209. this.pullRefreshText = '下拉刷新'
  210. this.pullRefreshImage = $r("app.media.ic_pull_refresh_down")
  211. this.isCanRefresh = false
  212. this.isRefreshing = false
  213. break;
  214. case 1:
  215. this.pullRefreshText = '松开刷新'
  216. this.pullRefreshImage = $r("app.media.ic_pull_refresh_up")
  217. this.isCanRefresh = true
  218. this.isRefreshing = false
  219. break;
  220. case 2:
  221. this.offsetY = vp2px(this.pullRefreshHeight)
  222. this.pullRefreshText = '正在刷新'
  223. this.pullRefreshImage = $r("app.media.ic_loading")
  224. this.isCanRefresh = true
  225. this.isRefreshing = true
  226. break;
  227. case 3:
  228. this.pullRefreshText = '刷新成功'
  229. this.pullRefreshImage = $r("app.media.ic_refresh_succeed")
  230. this.isCanRefresh = true
  231. this.isRefreshing = true
  232. break;
  233. }
  234. }
  235. // 关闭刷新
  236. closeRefresh() {
  237. // 如果允许刷新,延迟进入,为了显示刷新中
  238. setTimeout(() => {
  239. var delay = 50
  240. if (this.isCanRefresh) {
  241. // 状态3:刷新成功
  242. this.pullRefreshState(3)
  243. // 为了显示刷新成功,延迟执行收缩动画
  244. delay = 500
  245. }
  246. animateTo({
  247. duration: 150, // 动画时长
  248. delay: delay, // 延迟时长
  249. onFinish: () => {
  250. // 状态0:下拉刷新
  251. this.pullRefreshState(0)
  252. this.isPullRefreshOperation = false
  253. }
  254. }, () => {
  255. this.offsetY = 0
  256. })
  257. }, this.isCanRefresh ? 500 : 0)
  258. }
  259. //============================================加载更多==================================================
  260. // 手指移动,处理加载更多
  261. touchMoveLoadMore(event:TouchEvent) {
  262. // 因为加载更多是在列表后面新增一个item,当一屏能够展示全部列表,endIndex 为 length+1
  263. if (this.endIndex == this.list.length - 1 || this.endIndex == this.list.length) {
  264. // 滑动的偏移量
  265. this.offsetY = event.touches[0].y - this.downY
  266. if (Math.abs(this.offsetY) > vp2px(this.loadMoreHeight)/2) {
  267. // 可以刷新了
  268. this.isCanLoadMore = true
  269. // 显示加载更多布局
  270. this.isVisibleLoadMore = true
  271. // 偏移量缓慢增加
  272. this.offsetY = - vp2px(this.loadMoreHeight) + this.offsetY * 0.1
  273. }
  274. }
  275. }
  276. // 手指抬起,处理加载更多
  277. touchUpLoadMore() {
  278. animateTo({
  279. duration: 200, // 动画时长
  280. }, () => {
  281. // 偏移量设置为0
  282. this.offsetY = 0
  283. })
  284. if (this.isCanLoadMore) {
  285. console.info('======执行加载更多========')
  286. // 加载中...
  287. this.isLoading = true
  288. // 模拟耗时操作
  289. setTimeout(() => {
  290. this.closeLoadMore()
  291. this.loadMoreData()
  292. }, 2000)
  293. } else {
  294. console.info('======关闭加载更多!未达到条件========')
  295. this.closeLoadMore()
  296. }
  297. }
  298. // 关闭加载更多
  299. closeLoadMore() {
  300. this.isCanLoadMore = false
  301. this.isLoading = false
  302. this.isVisibleLoadMore = false
  303. }
  304. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/318648
推荐阅读
相关标签
  

闽ICP备14008679号