摘要:在上一阶段我们实现了基础的网络请求与列表展示,但对于一个成熟的移动端应用来说,仅仅展示数据是远远不够的。本文的核心任务是优化用户体验,引入现代化的列表交互方式——下拉刷新(Pull-to-Refresh)上拉加载更多(Infinite Scroll)。本文将详细讲解如何使用 easy_refresh 库在 OpenHarmony 平台上实现高性能的分页加载功能。

前言

上文中,我们成功请求到了 API 数据并展示在列表中。然而,当时的实现存在两个明显的缺陷:

  1. 数据一次性加载:如果服务器有 1000 条数据,一次性拉取会消耗大量流量和内存,甚至导致应用卡顿。
  2. 交互死板:用户无法手动更新数据,也无法浏览后续的内容。

为了解决这些问题,我们需要引入分页机制。


一、 技术选型与依赖配置

在 Flutter 生态中,实现刷新加载的方案有很多(如官方的 RefreshIndicator),但为了同时支持“下拉刷新”和“上拉加载”,且拥有更好的自定义能力,我们选择 easy_refresh 这个成熟的第三方库。

1. 添加依赖

打开项目根目录下的 pubspec.yaml,添加 easy_refresh 依赖:

dependencies:
  flutter:
    sdk: flutter
  dio: ^5.9.0
  provider: ^6.1.5+1
  easy_refresh: 3.3.0  # 3.4.0 版本与部分旧版 Flutter SDK 不兼容,建议锁定 3.3.0

在这里插入图片描述

添加后,记得在终端运行 flutter pub get 以安装依赖。
在这里插入图片描述


二、 核心代码改造

我们要从底层的数据获取开始,一直改造到顶层的 UI 展示,涉及 Service -> Provider -> UI

1. Service 层:支持分页参数

服务端通常通过 page(页码)或 offset(偏移量)来控制返回的数据。我们使用的 JSONPlaceholder API 支持 _start_limit 参数。

修改文件lib/services/api_service.dart

// 原有的 fetchPosts() 方法只支持获取全部
// 修改为支持传入 start 和 limit 参数
Future<List<Post>> fetchPosts({int start = 0, int limit = 10}) async {
  try {
    final response = await _dio.get(
      '$_baseUrl/posts',
      queryParameters: {
        '_start': start, // 起始位置
        '_limit': limit, // 每页条数
      },
    );
    // ... 解析逻辑保持不变
  } catch (e) {
    // ... 错误处理逻辑保持不变
  }
}

2. Provider 层:管理分页状态

Provider 需要记录当前加载到了第几页,是否还有更多数据,并区分“刷新”和“加载更多”两种行为。

修改文件lib/providers/post_provider.dart

我们引入了几个关键变量:

  • _page: 当前页码(或倍数)。
  • _hasMore: 标记是否还有数据,用于控制是否显示“没有更多了”。
  • _limit: 每页加载的数量(设为 10)。
class PostProvider extends ChangeNotifier {
  // ... 其他变量
  
  // 分页相关状态
  bool _hasMore = true;
  int _page = 0;
  final int _limit = 10;
  
  bool get hasMore => _hasMore;

  // 下拉刷新:重置所有状态
  Future<void> refresh() async {
    _isLoading = true;
    _page = 0;       // 重置页码
    _hasMore = true; // 重置更多标记
    notifyListeners();

    try {
      // 获取第一页数据
      final newPosts = await _apiService.fetchPosts(start: 0, limit: _limit);
      _posts = newPosts; // 覆盖原有数据
      _page = 1;         // 页码递增
      if (newPosts.length < _limit) {
        _hasMore = false; // 如果返回数据少于一页,说明到底了
      }
    } catch (e) {
      _errorMessage = e.toString();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  // 上拉加载:追加数据
  Future<void> loadMore() async {
    if (!_hasMore) return; // 如果没有更多,直接返回
    
    try {
      // 计算偏移量:当前页数 * 每页数量
      final newPosts = await _apiService.fetchPosts(start: _page * _limit, limit: _limit);
      
      if (newPosts.isEmpty) {
        _hasMore = false;
      } else {
        _posts.addAll(newPosts); // 关键:是 addAll 追加,不是覆盖
        _page++;
        if (newPosts.length < _limit) {
          _hasMore = false;
        }
      }
    } catch (e) {
      _errorMessage = e.toString();
    }
    notifyListeners();
  }
}

3. UI 层:集成 EasyRefresh

最后,我们在 UI 层将普通的 ListView 包裹在 EasyRefresh 组件中。

修改文件lib/pages/post_list_page.dart

import 'package:easy_refresh/easy_refresh.dart';

// ... State 类中

late EasyRefreshController _controller;


void initState() {
  super.initState();
  // 初始化控制器
  _controller = EasyRefreshController(
    controlFinishRefresh: true, // 由代码控制刷新结束
    controlFinishLoad: true,    // 由代码控制加载结束
  );
}


Widget build(BuildContext context) {
  return Scaffold(
    // ... AppBar
    body: Consumer<PostProvider>(
      builder: (context, provider, child) {
        // ... 错误处理逻辑
        
        return EasyRefresh(
          controller: _controller,
          header: const ClassicHeader(), // 经典的下拉刷新样式
          footer: const ClassicFooter(), // 经典的上拉加载样式
          
          // 下拉刷新回调
          onRefresh: () async {
            await provider.refresh();
            if (!mounted) return;
            _controller.finishRefresh(); // 告诉控件刷新完成了
            _controller.resetFooter();   // 重置底部的“没有更多”状态
          },
          
          // 上拉加载回调
          onLoad: () async {
            if (!provider.hasMore) {
              _controller.finishLoad(IndicatorResult.noMore); // 告诉控件没数据了
              return;
            }
            await provider.loadMore();
            if (!mounted) return;
            // 根据是否有更多数据,返回不同的结果状态
            _controller.finishLoad(
              provider.hasMore ? IndicatorResult.success : IndicatorResult.noMore
            );
          },
          
          child: ListView.builder(
            itemCount: provider.posts.length,
            itemBuilder: (context, index) {
              // ... 列表项渲染逻辑
            },
          ),
        );
      },
    ),
  );
}

三、 运行效果与体验优化

1. 运行效果

编译并在鸿蒙真机上运行应用:

  • 下拉:顶部会出现“下拉刷新” -> “释放刷新” -> “加载中”的状态变化,数据会重新获取。
  • 上拉:滑动到底部时,会自动触发加载更多,新的 10 条数据会平滑地追加到列表尾部。
  • 到底:当所有数据加载完毕(JSONPlaceholder 共 100 条),底部会显示“没有更多数据”。
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

2. 为什么不用 RefreshIndicator?

Flutter 自带的 RefreshIndicator 只能处理下拉刷新,对于上拉加载更多(Infinite Scroll),需要手动监听 ScrollController 的滚动位置,代码量大且容易出错(如抖动、重复请求)。EasyRefresh 封装了手势处理和状态机,极大地简化了开发流程。


四、 总结

虽然看似只是增加了一个交互功能,但其背后体现了移动端开发中至关重要的性能优化思想——分页加载

通过本次实战掌握了:

  1. 后端分页接口的对接:理解 start / limitpage / size 的参数意义。
  2. 状态管理的进阶:区分“刷新(重置)”与“加载(追加)”的数据流向。
  3. 第三方组件的鸿蒙适配:验证了 easy_refresh 在 OpenHarmony 上的完美运行。

接下来的任务,我们将继续完善应用,探索更复杂的界面布局与原生交互。


欢迎加入与关注

Logo

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

更多推荐