HarmonyOS 鸿蒙Next中如何实现下拉刷新功能

HarmonyOS 鸿蒙Next中如何实现下拉刷新功能

如何实现下拉刷新功能

3 回复

【背景知识】

  • PullToRefresh是一款OpenHarmony环境下可用的下拉刷新、上拉加载组件。支持设置内置动画的各种属性,支持设置自定义动画,支持lazyForEarch的数据作为数据源。
  • PullToRefresh使用限制:
    1. 目前只支持List、Scroll、Tabs、Grid和WaterFlow系统容器组件;
    2. 暂不支持设置系统容器组件的弹簧效果和阴影效果,使用时需要将系统组件edgeEffect属性的值设置为(EdgeEffect.None);
    3. 暂不支持页面触底时自动触发上拉加载功能;
    4. 暂不支持在页面数据不满一屏时触发上拉加载功能;
    5. 暂不支持通过代码的方式去触发下拉刷新功能;
    6. 暂不支持在下拉刷新动画结束时提供手势结束的回调。
  • Refresh:可以进行页面下拉操作并显示刷新动效的容器组件。
  • List组件:列表包含一系列相同宽度的列表项。
  • Button组件:按钮组件通常用于响应用户的点击操作,其类型包括胶囊按钮、圆形按钮、普通按钮、圆角矩形按钮。Button做为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮。
  • $$运算符:$$运算符为系统组件提供TS变量的引用,使得TS变量和系统组件的内部状态保持同步。

【解决方案】

  • 场景一解决方案: 在项目根目录下的oh-package.json5文件中配置PullToRefresh:
{
  // ...
  "devDependencies": {
    "[@ohos](/user/ohos)/pulltorefresh": "2.1.2"
  }
}

在模块目录下的oh-package.json5文件中配置PullToRefresh:

{
  "name": "entry",
  // ...
  "dependencies": {
    "[@ohos](/user/ohos)/pulltorefresh": "^2.1.2"
  }
}

示例代码实现:

import { PullToRefresh } from '[@ohos](/user/ohos)/pulltorefresh'

@Entry
@Component
struct ScrollCeilingTab {
  scroller: Scroller = new Scroller()
  private tabscroller: Scroller = new Scroller();
  @State itemData: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  tabTitles: Array<string> = ['Tab1', 'Tab2', 'Tab3']

  @Builder
  private getListView() {
    List({ space: 20, scroller: this.tabscroller }) {
      ForEach(this.itemData, (item: number) => {
        ListItem() {
          Text(`${item}`)
            .height(80)
            .width('100%')
            .textAlign(TextAlign.Center)
            .backgroundColor(0xDDDDDD)
            .margin({ bottom: 5 })
        }
      })
    }
    .nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.SELF_FIRST
    })
    .backgroundColor('#eeeeee')
    .divider({ strokeWidth: 1, color: 0x222222 })
    .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
  }

  @Builder
  tabContentData(tabTitle: string) {
    TabContent() {
      Column() {
        PullToRefresh({
          data: this.itemData,
          scroller: this.tabscroller,
          customList: () => {
            this.getListView();
          },
          // 可选项,下拉刷新回调
          onRefresh: () => {
            return new Promise<string>((resolve, reject) => {
              // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
              setTimeout(() => {
                resolve('刷新成功');
                let num = this.itemData.length
                this.itemData.push(num);
              }, 500);
            });
          },
          // 可选项,上拉加载更多回调
          onLoadMore: () => {
            return new Promise<string>((resolve, reject) => {
              setTimeout(() => {
                resolve('');
                let num = this.itemData.length
                this.itemData.push(num);
              }, 2000);
            });
          },
          customLoad: null,
          customRefresh: null,
        })
      }
    }
    .tabBar(tabTitle)
    .padding({ top: 5, bottom: 5 })
    .borderWidth(1)
    .borderColor(Color.Red)
  }

  build() {
    Column({ space: 10 }) {
      Scroll(this.scroller) {
        Column() {
          Image($r('app.media.startIcon')).height(70)
          Tabs() {
            ForEach(this.tabTitles, (title: string) => {
              this.tabContentData(title)
            })
          }.borderWidth(2)
        }.width('90%').alignItems(HorizontalAlign.Center)
      }.width('100%').align(Alignment.Center).scrollBar(BarState.Off)
    }
  }
}
  • 场景二解决方案: 可以使用$$双向绑定Refresh组件参数refreshing,点击按钮手动控制组件刷新状态实现,示例代码如下:
@Entry
@Component
struct RefreshExample {
  @State isRefreshing: boolean = false
  @State arr: String[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']

  build() {
    Column() {
      Row() {
        Button('开始刷新')
          .onClick(() => {
            // 手动开始刷新
            this.isRefreshing = true
          })
        Button('结束刷新')
          .onClick(() => {
            // 手动停止刷新
            this.isRefreshing = false
          })
      }
      Refresh({ refreshing: $$this.isRefreshing }) {
        List() {
          ForEach(this.arr, (item: string) => {
            ListItem() {
              Text('' + item)
                .width('70%')
                .height(80)
                .fontSize(16)
                .margin(2)
                .borderRadius(10)
                .backgroundColor(0xFFFFFF)
            }
          }, (item: string) => item)
        }
      }
      .onRefreshing(() => {
        // 2秒后自动结束刷新
        setTimeout(() => {
          this.isRefreshing = false
        }, 2000)
      })
      .backgroundColor(0x89CFF0)
    }
  }
}
  • 场景三解决方案: 可以在外层List使用Refresh组件实现上拉刷新,内层List使用pulltofresh进行下拉加载,代码示例如下:
import { PullToRefresh, PullToRefreshConfigurator } from '[@ohos](/user/ohos)/pulltorefresh'

const tabsList = ["能源", "化工", "塑料", "化纤", "聚氨酯"]

@Component
struct Index {
  private refreshScroller: Scroller = new Scroller();
  private refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator();
  @State list: Array<string> =
    ["a", "b", "c", "d", "e", "f", "a1", "b1", "c1", "d1", "e1", "f1"]

  aboutToAppear(): void {
    this.refreshConfigurator.setHasRefresh(false);
  }

  @Builder
  getListView() {
    List({ space: 10, scroller: this.refreshScroller }) {
      ForEach(this.list,
        (item: string, idx: number) => {
          ListItem() {
            Text(item)
              .width("100%")
              .height(200)
              .width('100%')
              .backgroundColor("#ffe0dbdb")
          }
        })
    }
    .height('100%')
    .width("100%")
    .scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.None)
    .nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.SELF_FIRST
    })
  }

  build() {
    Column({ space: 10 }) {
      PullToRefresh({
        // 必传项,列表组件所绑定的数据
        data: $list,
        refreshConfigurator: this.refreshConfigurator,
        // 必传项,需绑定传入主体布局内的列表或宫格组件
        scroller: this.refreshScroller,
        // 必传项,自定义主体布局,内部有列表或宫格组件
        customList: () => {
          // 一个用@Builder修饰过的UI方法
          this.getListView();
        },
        // 可选项,上拉加载更多回调
        onLoadMore: () => {
          return new Promise<string>((resolve, reject) => {
            // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
            setTimeout(() => {
              resolve('加载更多啦啦啦');
              this.list.push('a2');
              this.list.push('b2');
            }, 2000);
          });
        },
        customLoad: null,
        customRefresh: null,
      }).width('100%').height('100%')
    }.width('100%')
  }
}

@Entry
@Component
struct KeyboadPage {
  private refreshScroller: Scroller = new Scroller();
  @State isRefresh: boolean = false;

  build() {
    Column({ space: 10 }) {
      Refresh({ refreshing: $$this.isRefresh, builder: this.buildRefreshCom() }) {
        this.getListView()
      }.onRefreshing(() => {
        setTimeout(() => {
          this.isRefresh = false;
        }, 1000)
      })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildRefreshCom() {
    Row() {
      LoadingProgress().height(32)
      Text("正在刷新...").fontSize(16).margin({
        left: 20,
      })
    }
    .alignItems(VerticalAlign.Center)
  }

  @Builder
  getListView() {
    List({ scroller: this.refreshScroller }) {
      ListItem() {
        Text("第一条")
          .width("100%")
          .height(300)
          .backgroundColor("#ffe0dbdb")
      }

      ListItem() {
        Text("第二条")
          .width("100%")
          .height(500)
          .backgroundColor("#ff74a6ec")
      }

      ListItem() {
        Text("第三条")
          .width("100%")
          .height(200)
          .backgroundColor("#ffdbdfe0")
      }

      ListItem() {
        Tabs({
          barPosition: BarPosition.Start,
        }) {
          ForEach(tabsList,
            (item: string, idx: number) => {
              TabContent() {
                Index()
              }
              .tabBar(this.watchingTabBuilder(idx, item))
            })
        }
        .width("100%")
        .barHeight(50)
        .barMode(BarMode.Scrollable)
        .onChange((index: number) => {
        })
      }
    }
    .edgeEffect(EdgeEffect.None)
    .height('100%')
    .width('100%')
    .scrollBar(BarState.Off)
  }

  @Builder
  watchingTabBuilder(index: number, item: string) {
    Text(item)
      .fontSize(16)
      .fontColor("#121212")
      .padding({
        left: 15,
        right: 15,
        top: 12,
        bottom: 12
      })
      .borderRadius(5)
  }
}

【常见FAQ】 Q:页面的数据已经全部加载完成时,如何结束上拉加载的状态?

A:将PullToRefreshConfigurator类的setHasLoadMore属性为false,即关闭上拉加载功能。

Q:除了使用PullToRefresh三方组件,是否还有其他方式实现List下拉刷新?

A:HarmonyOS提供了Refresh组件,该组件是进行页面下拉操作并显示刷新动效的容器组件。

Q:除了使用PullToRefresh三方组件,是否还有其他方式实现List上拉加载?

A:可以通过监听List组件的滚动状态手动实现数据加载的逻辑,示例代码为:

@Entry
@Component
struct Index {
  @State nums: number[] = Array(20).fill(Date.now())
  isReachEnd: boolean = false
  scroller: Scroller = new Scroller()

  build() {
    List({ scroller: this.scroller }) {
    }
    // 开始滚动
    .onScrollStart(() => {
      this.isReachEnd = false
    })
    // 滚动结束
    .onScrollStop(() => {
      if (this.isReachEnd) {
        setTimeout(() => {
          let newNums: number[] = Array(2).fill(Date.now())
          this.nums.push(...newNums)
          this.scroller.scrollEdge(Edge.End)
          promptAction.showToast({
            message: '新加载2条'
          })
        }, 2000)
      }
    })
    // 到达滚动底部
    .onReachEnd(() => {
      this.isReachEnd = true
    })
  }
}

更多关于HarmonyOS 鸿蒙Next中如何实现下拉刷新功能的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,下拉刷新功能主要通过Refresh组件实现。该组件是ArkUI提供的标准容器组件,用于为可滚动组件(如ListScroll)添加下拉刷新交互。

核心实现步骤:

  1. 使用Refresh组件包裹你的可滚动列表(如List)。
  2. 通过RefreshonRefresh事件回调来触发数据刷新逻辑。
  3. 在回调中执行你的数据加载或更新操作。
  4. 操作完成后,调用RefreshControllerfinishRefresh方法结束刷新状态并更新UI。

示例代码结构:

@Entry
@Component
struct Index {
  private controller: RefreshController = new RefreshController()

  build() {
    Refresh({ controller: this.controller, onRefresh: () => {
      // 执行你的数据刷新逻辑
      // ...
      // 刷新完成后
      this.controller.finishRefresh()
    }}) {
      List() {
        // 你的列表项内容
      }
    }
  }
}

在HarmonyOS Next中,实现下拉刷新功能主要依赖于Refresh组件。这是一个高度封装的容器组件,可以轻松地为列表(如ListGrid等)添加下拉刷新和上拉加载的能力。

以下是实现下拉刷新的核心步骤和代码示例:

1. 基本使用

Refresh组件包裹在你的可滚动组件(如List)外部,并通过onRefresh回调触发数据刷新逻辑。

import { Refresh, List, ListItem, Text } from '@kit.ArkUI';

@Entry
@Component
struct RefreshExample {
  @State data: string[] = ['Item 1', 'Item 2', 'Item 3'];
  @State isRefreshing: boolean = false; // 控制刷新状态

  build() {
    Column() {
      // 使用Refresh组件包裹List
      Refresh({
        onRefresh: () => {
          // 触发刷新时的回调
          this.isRefreshing = true;
          // 模拟网络请求,2秒后更新数据并结束刷新
          setTimeout(() => {
            this.data = ['New Item 1', 'New Item 2', 'New Item 3'];
            this.isRefreshing = false;
          }, 2000);
        },
        isRefreshing: this.isRefreshing // 绑定刷新状态
      }) {
        List() {
          ForEach(this.data, (item: string) => {
            ListItem() {
              Text(item)
                .fontSize(20)
                .margin(10)
            }
          })
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
  }
}

2. 关键属性说明

  • onRefresh: () => void:用户下拉触发刷新时执行的回调函数。你应在此函数中执行数据加载逻辑,并在完成后将isRefreshing设置为false
  • isRefreshing: boolean:控制刷新状态的变量。设置为true时显示加载指示器,数据加载完成后应设置为false以隐藏指示器。
  • offset: Length(可选):触发onRefresh回调的下拉距离阈值,默认为30vp

3. 自定义刷新指示器

你可以通过builder属性自定义下拉时显示的UI,实现品牌化的加载动画。

Refresh({
  onRefresh: this.handleRefresh,
  isRefreshing: this.isRefreshing,
  builder: (refreshState: RefreshStatus) => {
    // 根据状态自定义显示内容
    if (refreshState === RefreshStatus.Inactive) {
      return Text('下拉刷新'); // 初始状态
    } else if (refreshState === RefreshStatus.Drag) {
      return Text('释放立即刷新'); // 下拉中
    } else if (refreshState === RefreshStatus.Refresh) {
      return Text('正在刷新...'); // 刷新中
    } else {
      return Text('刷新完成'); // 刷新完成
    }
  }
}) {
  // 你的列表内容
}

4. 配合上拉加载

Refresh组件也支持上拉加载更多功能,通过onLoadMoreisLoadingMore属性实现,用法与下拉刷新类似。

注意事项

  • 确保isRefreshing状态在数据加载完成后及时更新,否则加载指示器会一直显示。
  • 列表内容需要有足够的高度才能触发下拉手势,如果数据过少,可以暂时设置一个最小高度。
  • Refresh组件目前主要适用于ListGridScroll等可滚动组件。

通过上述方式,你可以高效地在HarmonyOS Next应用中集成标准或自定义的下拉刷新交互。

回到顶部