学习目标:

本帖主要学习和交流通过Flutter框架实现鸿蒙App 列表的上架加载和下拉刷新的功能

这篇帖子是基于上一篇【开源鸿蒙跨平台开发先锋训练营】使用Flutter请求网络接口并渲染页面 的基础上学习和开发的。
公开代码也可以前往【开源鸿蒙跨平台开发先锋训练营】使用Flutter框架搭建鸿蒙App项目及代码管理中查看

下拉刷新与上拉加载(分页,每页 10 条)

概述

本次修改在 HomePage 中添加了下拉刷新与上拉加载(本地分页,每页 10 条),支持两种展示模式:单列垂直 PageView 与两列 GridView。目的是:平滑的用户交互、自动按需加载、并提供简单的保护以避免重复触发。

变更清单

  • 为实现上拉加载和下拉刷新刷新,特新增了下面的属性:

    • _displayImages:当前用于展示的分页图片列表。
    • _pageSize:每页条数,设为 10。
    • _currentPage_hasMore_isLoadingMore:分页状态控制。
    • _gridScrollController:用于 GridView 的滚动监听以实现上拉加载。
     // 原始完整图片列表(从 API 解析得到)
    List<String> _images = [];
    // 当前用于展示的分页图片列表
    List<String> _displayImages = [];
    // 分页相关
    final int _pageSize = 10; // 每页 10 条
    int _currentPage = 0; // 已加载页数
    bool _hasMore = false; // 是否还有更多数据
    bool _isLoadingMore = false; // 是否正在加载更多
    
    bool _isLoading = true;
    String? _errorMessage;
    int _currentIndex = 0;
    bool _isGridView = false; // 显示模式:false=单列滑动,true=2列网格
    final ScrollController _gridScrollController = ScrollController();
    
  • 新增方法:

    • _refreshData():下拉刷新,重置分页并重新请求数据。
    • _loadMore():计算 slice 并追加到 _displayImages,更新分页状态。
    • _onGridScroll():GridView 滚动监听方法,在接近底部时触发 _loadMore()
    /// 下拉刷新,重新请求并重置分页
    Future<void> _refreshData() async {
      setState(() {
        _errorMessage = null;
        _isLoading = true;
      });
      _displayImages.clear();
      _currentPage = 0;
      _hasMore = false;
      await _loadData();
    }
    
    /// 加载下一页数据(内部分页)
      Future<void> _loadMore() async {
        if (_isLoadingMore) return;
        if (_images.isEmpty) return;
    
        setState(() {
          _isLoadingMore = true;
        });
    
        // 计算下一页的区间并追加到展示列表
        final nextPage = _currentPage + 1;
        final start = (nextPage - 1) * _pageSize;
        final end = (start + _pageSize) < _images.length ? (start + _pageSize) : _images.length;
    
        // 如果没有新数据可加,直接结束
        if (start >= _images.length) {
          setState(() {
            _hasMore = false;
            _isLoadingMore = false;
          });
          return;
        }
    
    	void _onGridScroll() {
    	  if (_gridScrollController.position.extentAfter < 300 && _hasMore && !_isLoadingMore) {
    	     _loadMore();
    	   }
    	 }
    
  • 更新方法:

    • _loadData():由原来直接将全部解析结果赋值,改为先保存完整 _images,然后调用 _loadMore() 加载第一页(每页 10 条)。
    • initState():添加 _gridScrollController 监听器。
    • dispose():释放 _gridScrollController
/// 加载数据
  Future<void> _loadData() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      print('开始加载数据...');
      
      // 1. 获取原始 JavaScript 数据
      print('正在请求网络数据...');
      final jsData = await _apiService.getMockData();
      print('网络请求完成,数据类型: ${jsData.runtimeType}');
      
      // 2. 确保数据是字符串格式
      final jsContent = jsData is String ? jsData : jsData.toString();
      print('数据长度: ${jsContent.length}');
      
      // 3. 解析数据并提取图片
      print('开始解析数据...');
      final result = await DataParser.parseWorksAndImages(jsContent);
      print('数据解析完成');
      
      // 4. 获取所有图片 URL
      final allImages = result['images'] as List<String>;
      
      // 调试:打印图片数量和前几个URL
      print('加载到 ${allImages.length} 张图片');
      if (allImages.isNotEmpty) {
        print('第一张图片URL: ${allImages[0]}');
        print('第一张图片URL长度: ${allImages[0].length}');
        print('第一张图片URL是否为空: ${allImages[0].isEmpty}');
      } else {
        print('警告:图片列表为空!');
      }
      
      // 将原始列表保存,并初始化分页展示数据(每页 10 条)
      _images = allImages;
      _displayImages.clear();
      _currentPage = 0;
      _hasMore = _images.length > 0;

      // 加载首页数据
      await _loadMore();

      setState(() {
        _isLoading = false;
      });

      print('数据加载完成,UI已更新');
    } catch (e, stackTrace) {
      print('加载数据时出错: $e');
      print('错误堆栈: $stackTrace');
      setState(() {
        _errorMessage = '加载失败: $e';
        _isLoading = false;
      });
    }
  }
  • UI 修改:
    • 在主体最外层使用 RefreshIndicator 以支持下拉刷新。
    • 使用 _displayImages 作为 PageViewGridView 的数据源。
    • _isLoadingMore 为 true 时在底部显示圆形进度作为加载指示器。
    • PageView 在接近已加载末尾(index >= length - 2)时触发 _loadMore()

关键字段与方法说明

  • 原始与展示列表:
    • _images(全部数据)
    • _displayImages(已加载用于展示)
  • 分页配置:_pageSize = 10
  • 加载控制:_currentPage, _hasMore, _isLoadingMore
  • 刷新:_refreshData() — 完整重置并调用 _loadData()
  • 加载更多:_loadMore() — 计算起止索引并追加到 _displayImages,更新 _currentPage_hasMore

网格滚动监听:_gridScrollController + _onGridScroll()(当 extentAfter < 300 时触发)

参见实现:lib/pages/home_page.dart

用户可感知的行为

  • 首次加载:页面启动或 HomePage 打开时,_loadData() 请求数据并以本地分页的方式加载第一页(最多 10 条)。
  • 下拉刷新:任意可滚动视图下拉会触发 RefreshIndicator 调用 _refreshData(),完成后显示最新第一页数据。
  • 上拉加载(Grid):在网格视图滚动到底部时,会自动加载更多数据并追加到当前列表。
  • 上拉加载(PageView):在翻页到接近当前已加载末尾时会自动加载下一页。
  • 加载状态:加载更多时会在页面底部显示一个小圆形进度条。

测试步骤

  1. 运行应用并进入首页。
    在这里插入图片描述

  2. 验证首次加载:看到加载指示后展示不超过 10 张图片。

  3. 下拉刷新:在任一视图向下拉,观察是否重新请求并回到第一页数据。
    在这里插入图片描述

  4. Grid 上拉:切换到 Grid,滚动到底部,确认触发加载并追加新图片(直至无更多)。
    在这里插入图片描述

  5. PageView 自动加载:市面上大部分应用都做了这块体验上的优化,当接近末尾应自动加载下一页。

  6. 边界测试:当总数据 < 10 时确认无额外请求;当数据为空时显示“暂无数据”。

以下几点需要注意

  • 当前实现是先解析并保留完整图片 URL 列表(_images),然后在客户端按页加载,这会占用内存。,如果有合适的后端分页接口,改为服务端分页以减少内存与初始延迟。
  • 已用 _isLoadingMore 防止重复触发。

结束语

感谢阅读本帖,如对贴中内容有意见和建议的,欢迎与我联系交流,也欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐