题记

上一篇实现了flutter → OH模拟器 →服务器 请求的一个完整的例子。但是比较简单,只是实现了基本的请求功能,但是我们做app 的话,上拉加载你得有吧,下拉刷新你也得有吧。请求过程的中的友好交互你得做吧。
哇呀呀呀 , 开干。

思路

  1. 引入pull_to_refresh库来实现app 的上拉和下拉功能
  2. 后端增加大量的 mock 数据,以支撑多次上拉效果。
  3. 为了让交互更加的丝滑,加入请求骨架屏(替换之前的 loading)、加载失败、无更多数据、空数据等状态。

实现上拉和下拉功能

安装 pull_to_refresh 库

为什么选择pull_to_refresh库呢?
“如果说原生组件是‘能用’,那么 pull_to_refresh 就是‘好用且全能’——它把繁杂的状态判断封装成了简单的回调,让开发者能专注于业务逻辑而非滚动算法。”
作为 Flutter 社区的老牌插件,它的兼容性极强,支持所有的 ScrollView(ListView, GridView, CustomScrollView),是目前最省心的方案。
在这里插入图片描述

添加下拉效果

  1. 添加 Flutter 下拉效果的代码
    这里的下拉以及上拉的效果都是使用SmartRefresher来包裹其中的展示数据,也即是包裹整个页面。
      body: Scaffold(
        body: SmartRefresher(
          // 是否允许下拉刷新
          enablePullDown: true,
          // 是否允许上拉加载更多
          enablePullUp: true,
          // 设定下拉刷新的头部样式(这里使用的是水滴效果)
          // ; header: WaterDropHeader(),
          header: classicIndicator(),
          footer: footerIndicator(),
          // 控制器,用于手动触发刷新或停止刷新动画
          controller: _refreshController,
          // 下拉触发的回调函数
          onRefresh: _onRefresh,
          // 上拉触发的回调函数
          onLoading: _onLoading,
          child: _buildBody(),
        ),
      ),

重点是,这里我们要注册要给控制器给到 SmartRefresher 中 controller 选项。

final RefreshController _refreshController = RefreshController(
    initialRefresh: false, // 之前我们已经在页面中添加了 initState 这里就不改了
);
  1. 修改后端接口
    原来的接口,如果只是用来下拉更新的话是没问题的,但是涉及到了上拉加载,这就涉及到了分页,我们对原有的数据进行改造一番。并加两个一个 5s 的延迟,毕竟是本地的环境,加载太快了 看不到 loading 的效果。
fastify.post('/load', async function handler(request, reply) {
  const page = request.body.page ?? 1;
  const pagesize = request.body.pagesize ?? 5;
  const data = [
    { hello: 'world' },
    { hello: 'harmony' },
    { hello: 'sqllite' },
    { hello: 'ArkUI' },
    { hello: 'ArkTS' },
    { hello: 'DevEco Studio' },
    { hello: 'Ability' },
    { hello: 'Service Extension Ability' },
    { hello: 'Distributed Soft Bus' },
    { hello: 'Atomic Service' },
    { hello: 'HarmonyOS SDK' },
    { hello: 'OpenHarmony' },
    { hello: 'HAP' }
  ]
  await new Promise(resolve => setTimeout(resolve, 5000));

  return {
    code: 200,
    message: 'success',
    data: {
      list: data.slice((page - 1) * pagesize, page * pagesize),
      total: data.length,
      page,
      pagesize
    }
  };
})
  1. 进行下拉刷新的步骤
    首先我们要对上面的下拉效果代码进行补全,我这里只记录一下classicIndicator(), footerIndicator(), 这两个函数。
Widget classicIndicator() {
    return const ClassicHeader(
      idleText: '下拉刷新',
      releaseText: '释放刷新',
      refreshingText: '正在刷新...',
      completeText: '刷新完成',
      failedText: '刷新失败',
    );
  }

  Widget footerIndicator() {
    return CustomFooter(
      builder: (context, mode) {
        Widget body;
        // 状态 1:闲置状态,等待用户上拉
        if (mode == LoadStatus.idle) {
          body = Text("上拉加载更多");
        }
        // 状态 2:正在加载数据中
        else if (mode == LoadStatus.loading) {
          //   body = CupertinoActivityIndicator(); // 显示 iOS 风格的加载菊花
          body = Text("正在加载...");
        }
        // 状态 3:加载失败
        else if (mode == LoadStatus.failed) {
          body = Text("加载失败!点击重试");
        }
        // 状态 4:上拉位移足够,释放即可开始加载
        else if (mode == LoadStatus.canLoading) {
          body = Text("释放开始加载");
        }
        // 状态 5:没有更多数据了
        else {
          body = Text("没有更多数据了");
        }
        debugPrint('🚀 [Refresh Status]: $mode - $body');

        // 返回页脚的容器,固定高度 55 像素并居中显示内容
        return SizedBox(height: 55.0, child: Center(child: body));
      },
    );
  }

这里我注意到两个函数,是不一样的。上拉的时候还要手动控制?这里显然不合理,查了下源码,发现有对应的 ClassicFooter 函数,也就是说不用手动去控制了(可以偷懒了)。

    return ClassicFooter(
      // 加载状态的文字
      loadingText: "正在努力加载中...",
      // 准备加载时的文字
      canLoadingText: "松开即可加载更多",
      // 闲置状态(上拉前)的文字
      idleText: "上拉加载更多",
      // 没有更多数据时的文字
      noDataText: "到底啦,没有更多数据了",
      // 加载失败时的文字
      failedText: "加载失败,请重试",
      height: 60.0,
    );

哦哦哦 最重要的是,我们接口已经变化了,这时候要调整一下请求的接口函数,添加,page 以及 pagesize。

  Future<void> loadData({int page = 1, int pagesize = 5}) => _handleRequest(
    dio.post('$HOST/load', data: {'page': page, 'pagesize': pagesize}),
    isAppend: page > 1,
  );

查看效果以及解决问题

我们请求一下看看效果,好像出了点问题。忘记处理返回类型了。
在这里插入图片描述

// 原来的类型处理
final response = await request;
setState(() {
   // 假设返回的是一个 List,如果不是,需要根据具体 JSON 结构解析
   _data = response.data as List<dynamic>;
   _isLoading = false;
 });
 // 调整之后
 // 服务端返回结构为 { code: 200, data: { list: [], total: 13, ... } }
final Map<String, dynamic> responseData =
    response.data as Map<String, dynamic>;
final List<dynamic> result =
    responseData['data']['list'] as List<dynamic>;

这里再次请求之后又出现了一个问题报错。
在这里插入图片描述
提示 404 了,我们看看后端日志报了什么错。
在这里插入图片描述
哈哈 干了件蠢事,想着后面会继续用到我们的 HOST,就单独提取出来到一个配置文件里。但是拼接的时候忘记还多了一个 / ,这可就要了命了。

// 这里采用这样的写法,多少一个/无所谓了,即使你习惯前面加个 / 
Uri.parse(HOST).resolve('/load').toString(),

下拉刷新这次真的ok了

在这里插入图片描述
这里还出了个小岔子,多了个大 loading 框框,还需要我们把之前加的全局 loading 框框给去掉。
在这里插入图片描述
OK 到这里下拉刷新的效果就 OK 了。

上拉加载也OK了

解决了之前的全局 loading 情况,上拉加载的也好看了很多。
在这里插入图片描述
这里还有一个事情要注意,后端已经返回了 total 数据,需要我们判断一下,否则没有拉到底的文字提示。

  void _onRefresh() async {
    final success = await loadData();
    if (success) {
      _refreshController.refreshCompleted();
      // 如果数据已经加载完,显示“到底啦”
      if (_data.length >= _total) {
        _refreshController.loadNoData();
      } else {
        _refreshController.resetNoData(); // 重置加载状态
      }
    } else {
      _refreshController.refreshFailed();
    }
  }

到这里的话 网络请求已经全部结束了。具体的代码已经发到 AutoGit上,有 兴趣可以下来看看。

示例地址,点击跳转到gitcode

总结

我真傻,真的!我单知道…
之前两篇,我直接把 git ssh 地址当做 http 链接贴上去了,今天才发现。现在都已经修复了。
本次上拉和下拉使用的是pull_to_refresh库,使用上比较简单,基本上没有因为配置的问题查很久的文档,在鸿蒙的虚拟机上演示也是很丝滑。
遇到的问题也是都是一些细节没有仔细导致的,大家练手的时候注意前后端的数据解构配合哦。

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐