【开源鸿蒙跨平台开发先锋训练营】DAY4~DAY6 OpenHarmony版Flutter本地美食清单上拉加载 + 下拉刷新 + 数据加载提示实现

【开源鸿蒙跨平台开发先锋训练营】DAY4~DAY6 OpenHarmony版Flutter本地美食清单上拉加载 + 下拉刷新 + 数据加载提示实现

目录

[【开源鸿蒙跨平台开发先锋训练营】DAY4~DAY6 OpenHarmony版Flutter本地美食清单上拉加载 + 下拉刷新 + 数据加载提示实现](#【开源鸿蒙跨平台开发先锋训练营】DAY4~DAY6 OpenHarmony版Flutter本地美食清单上拉加载 + 下拉刷新 + 数据加载提示实现)

[摘 要](#摘 要)

[1 概述](#1 概述)

[1.1 开发背景](#1.1 开发背景)

[1.2 开发目标](#1.2 开发目标)

[1.3 核心技术栈](#1.3 核心技术栈)

[2 核心功能实现](#2 核心功能实现)

[2.1 功能核心定位](#2.1 功能核心定位)

[2.2 基础配置准备](#2.2 基础配置准备)

[2.2.1 开发环境](#2.2.1 开发环境)

[2.2.2 核心依赖配置](#2.2.2 核心依赖配置)

[2.2.3 配置本地模拟美食API](#2.2.3 配置本地模拟美食API)

[3 API层:分页请求封装](#3 API层:分页请求封装)

[3.1 实现目标](#3.1 实现目标)

[3.2 具体修改内容](#3.2 具体修改内容)

[3.3 功能作用](#3.3 功能作用)

[4 页面层:核心逻辑实现](#4 页面层:核心逻辑实现)

[4.1 实现目标](#4.1 实现目标)

[4.2 状态变量定义(页面层核心基础)](#4.2 状态变量定义(页面层核心基础))

[4.3 下拉刷新逻辑实现](#4.3 下拉刷新逻辑实现)

[4.4 上拉加载逻辑实现](#4.4 上拉加载逻辑实现)

[4.5 数据加载提示实现](#4.5 数据加载提示实现)

[5 测试与验证](#5 测试与验证)

[5.1 测试环境](#5.1 测试环境)

[5.2 功能验证](#5.2 功能验证)

[6 代码提交与 AtomGit 仓库更新](#6 代码提交与 AtomGit 仓库更新)

[6.1 查看本地修改](#6.1 查看本地修改)

[6.2 暂存本地修改](#6.2 暂存本地修改)

[6.3 提交本地修改](#6.3 提交本地修改)

[6.4 拉取远程仓库最新代码](#6.4 拉取远程仓库最新代码)

[6.5 推送更新到 AtomGit 仓库](#6.5 推送更新到 AtomGit 仓库)

[6.6 验证推送结果](#6.6 验证推送结果)

[7 总结与展望](#7 总结与展望)

[7.1 总结](#7.1 总结)

[7.2 扩展方向](#7.2 扩展方向)


摘 要

本文基于开源鸿蒙跨平台开发框架,针对本地美食清单应用的列表功能进行扩展优化。完成列表下拉刷新、上拉加载、数据加载提示三大能力实现,并在DevEco Studio上运行验证。通过Dio封装分页API请求(food_api.dart)、pull_to_refresh实现下拉交互、infinite_scroll_pagination管理上拉分页逻辑,结合状态驱动UI设计加载提示;最终通过在DevEco Studio(模拟器)上验证功能兼容性,并按Git规范将完整工程推送到AtomGit公开仓库,确保代码可复现、可复用。

1 概述

1.1 开发背景

Flutter是Google开发的跨平台UI框架,用于通过一套代码库高效构建跨平台应用。‌它采用Dart 编程语言驱动,支持将应用编译为原生机器代码或 JavaScript,从而实现高性能渲染和多端一致性。Flutter 的核心优势在于‌一次开发、多端部署‌,支持移动(iOS/Android)、Web、桌面(Windows/macOS/Linux)及嵌入式设备。OpenHarmony版Flutter 是Flutter针对OpenHarmony系统的适配版本,可以让你用Flutter开发HarmonyOS/OpenHarmony应用。而"本地美食清单"作为生活类应用场景,之前已经实现网络数据请求、图片加载优化、列表交互等核心功能,因此本文还需实现分页加载、刷新交互、状态提示三大核心能力。

1.2 开发目标

  1. 完成列表下拉刷新:触发重新获取第一页数据,重置分页状态;

  2. 实现列表上拉加载更多:支持分页参数传递,增量加载数据;

  3. 数据加载提示:包含初始化加载、刷新中、加载中、加载失败、无更多数据;

  4. 完成功能验证:确保功能在模拟器上正常运行;

  5. 落地代码提交规范:按Git标准推送完整工程到AtomGit,保障可复现性。

1.3 核心技术栈

基于原有技术栈,新增/优化以下依赖与组件:

|----------------------------|---------------|---------------------------------------|
| 技术组件 | 版本 | 用途说明 |
| Flutter | 3.27.4(鸿蒙适配版) | 跨平台UI框架,实现列表UI与交互 |
| Dio | 5.0.0 | 网络请求库,封装分页API请求 |
| pull_to_refresh | ^2.0.0 | 下拉刷新交互组件,适配鸿蒙触控逻辑(也可以统一实现下拉刷新与上拉加载功能) |
| infinite_scroll_pagination | 4.0.0 | 上拉分页管理组件,简化分页状态维护 |
| SnackBar | Flutter内置 | 操作结果与异常提示 |

2 核心功能实现

2.1 功能核心定位

1. 明确页面目标:

基于开源鸿蒙跨平台工程(Flutter+OpenHarmony) 继续开发"本地美食数据清单"页面。

2. 核心目标:

为开源鸿蒙跨平台工程的本地美食列表清单实现上拉加载、下拉刷新及数据加载提示能力,并在DevEco Studio 6.0.0模拟器中完成"上拉加载→下拉刷新"功能运行验证,达成跨平台工程的"上拉加载 + 下拉刷新"能力落地。

2.2 基础配置准备

2.2.1 开发环境

|-----------------|--------------------------|---------------------|
| 开发工具/环境 | 版本规格 | 用途说明 |
| 操作系统 | Windows 10 64 位 | 开发主机运行环境 |
| VS Code | 1.108.1(user setup) | Flutter 业务代码编写与依赖管理 |
| DevEco Studio | 6.0.0 Release | OpenHarmony 应用配置与部署 |
| OpenHarmony SDK | API Version 20(6.0.0.47) | 鸿蒙应用开发核心依赖 |
| Flutter | 3.27.4(鸿蒙适配版) | 跨平台 UI 框架 |

2.2.2 核心依赖配置

在 VS Code 的终端中(需确保终端路径处于你的 Flutter 工程根目录下),执行以下命令来添加这两个依赖:

**注意:**在这里博主虽然配置了easy_refresh库,但本文中并没有用到,仅使用了
pull_to_refresh (Flutter 主流下拉刷新 / 上拉加载库,支持自定义加载动画,适配鸿蒙 Flutter 引擎)、infinite_scroll_pagination(专注上拉分页加载,适配鸿蒙数据请求异步逻辑)。
OpenHarmony已兼容三方库清单https://gitcode.com/openharmony-tpc/flutter_packages

复制代码
flutter pub add infinite_scroll_pagination:^4.0.0
flutter pub add easy_refresh

操作步骤说明:

  1. 打开 VS Code 的终端:点击顶部菜单栏"终端"→新建终端;

  2. 确认终端路径:确保终端当前路径是你的 Flutter 工程根目录(比如博主这里显示 PS D:\Flutter_HmProject\flutter_harmonyos> 这样的工程路径);

  3. 执行上述命令:命令会自动将这两个依赖添加到 pubspec.yaml 的 dependencies 中,并下载对应的包资源。

执行完成后,pubspec.yaml 里会自动新增这两个依赖项(无需手动修改),可以直接开始后续的逻辑。

也可在添加后执行flutter pub get安装依赖。

完成上述依赖添加后,博主的pubspec.yaml 的 dependencies 中显示如下:

复制代码
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8
  http: ^1.6.0                         # 网络请求核心库(鸿蒙兼容)
  cached_network_image: ^3.4.1         # 美食图片缓存(解决鸿蒙设备图片重复加载卡顿)
  pull_to_refresh: ^2.0.0              # 下拉刷新(适配鸿蒙触控交互)
  flutter_rating_bar: ^4.0.1           # 美食评分组件(鸿蒙UI适配)
  dio: 5.0.0
  json_annotation: ^4.9.0
  infinite_scroll_pagination: 4.0.0    # 专注上拉分页(适配鸿蒙异步逻辑)
  easy_refresh: ^3.4.0                 # 轻量化刷新(适配鸿蒙触控交互)

2.2.3 配置本地模拟美食API

博主继续使用上期Node.js + Express实现本地模拟美食 API。

1. 编写本地接口代码

在local_food_api文件夹中优化server.js文件,编写以下代码,添加一些新的数据:

复制代码
// 顶部引入cors
const cors = require('cors');
// 导入Express
const express = require('express');
const app = express();
// 配置静态资源目录:让public文件夹里的文件可以通过HTTP访问
app.use(express.static('public'));
// 启用CORS(允许所有域名访问,测试用)
app.use(cors());
// 定义端口(可自定义,比如3000)
const port = 3000;

// 模拟美食列表数据
const mockFoodData = [
  {
    id: 1,
    name: "花生松仁炒蛋",
    desc: "鸡蛋+松仁,鲜嫩可口",
    image: "/food1.jpg", // 对应public里的food1.jpg
    score: 4.8
  },
  {
    id: 2,
    name: "白玉木耳炒西蓝花",
    desc: "白玉木耳滑嫩脆弹,西蓝花清甜脆爽",
    image: "/food2.jpg",
    score: 4.9
  },
  {
    id: 3,
    name: "糖醋排骨",
    desc: "甜甜蜜蜜的糖醋排骨",
    image: "/food3.jpg",
    score: 4.5
  },
  {
    id: 4,
    name: "一品红烧肉",
    desc: "一碗直达灵魂的红烧肉,肥而不腻,入口即化",
    image: "/food4.jpg",
    score: 4.5
  },
  {
    id: 5,
    name: "自制鸡肉丸",
    desc: "肉嫩鲜香,而且营养丰富",
    image: "/food5.jpg",
    score: 5.0
  },
  {
    id: 6,
    name: "胡萝卜炒鸡蛋",
    desc: "胡萝卜、鸡蛋、香葱、花生油、盐",
    image: "/food6.jpg",
    score: 4.8
  },
  {
    id: 7,
    name: "红烧茄子",
    desc: "红烧茄子口味浓郁",
    image: "/food7.jpg",
    score: 4.7
  },
  {
    id: 8,
    name:"鱼香茄子",
    desc:"鱼香茄子是一道川菜,具有咸、酸、甜、辣、香、鲜和浓郁的葱、姜、蒜味的特色。酸酸甜甜的最是开胃。",
    image:"/food8.jpg",
    score: 4.0
  },
  {
    id: 9,
    name:"凉拌银耳",
    desc:"菜品酸辣开胃,清淡爽口,入味十足",
    image:"/food9.jpg",
    score: 4.0
  },
  {
    id: 10,
    name:"鱼香肉丝",
    desc:"鱼香肉丝是一道经典的中国汉族传统名菜",
    image:"/food10.jpg",
    score: 5.0
  },
  {
    id: 11,
    name:"麻婆豆腐",
    desc:"麻婆豆腐是四川省汉族传统名菜之一,属于川菜系",
    image:"/food11.jpg",
    score: 4.5
  },
  {
    id: 12,
    name:"宫保鸡丁",
    desc:"红而不辣、辣而不猛、香辣味浓、肉质滑脆",
    image:"/food12.jpg",
    score: 4.3
  }
];

// 定义美食列表接口:GET请求,路径为/api/localFood/list
app.get('/api/localFood/list', (req, res) => {
  // 1. 获取前端传递的分页参数(默认page=1,pageSize=10)
  const page = parseInt(req.query.page) || 1;
  const pageSize = parseInt(req.query.pageSize) || 10;  // 统一为pageSize(驼峰)

  // 2. 计算分页的"起始/结束索引",对mock数据切片
  const startIndex = (page - 1) * pageSize;
  const endIndex = startIndex + pageSize;
  // const paginatedFoodList = mockFoodData.slice(startIndex, endIndex);
  const paginatedFoodList = [];
  // 循环取数:当索引超过mock数据长度时,用"取模"循环重复数据
  for (let i = startIndex; i < endIndex; i++) {
    const loopIndex = i % mockFoodData.length; // 取模实现循环
    paginatedFoodList.push(mockFoodData[loopIndex]);
  }
  // 3. 返回"包含foodList字段的对象"(前端要这个格式)
  res.json({
    foodList: paginatedFoodList // 必须是"对象包裹数组"
  });
});

// 启动服务
app.listen(port, () => { // 改为用port变量
  console.log(`Server running on http://localhost:${port}`);
});

2. 启动本地接口服务

在local_food_api文件夹的终端中执行以下命令:

复制代码
node server.js

看到 "Server running on http://localhost:3000" 即成功。

  1. 浏览器访问http://localhost:3000/api/localFood/list验证接口数据(注意:localhost也可以改为你本地的ipv4/IP地址访问)。

因为这次定义了分页逻辑,所以只能显示前10条数据,在浏览器地址栏输入http://localhost:3000/api/localFood/list?page=2,才能够显示后续数据。

3 API层:分页请求封装

API层:分页请求封装,修改文件:lib/api/food_api.dart。

3.1 实现目标

为"美食列表请求"添加分页参数(page/pageSize),通过Dio传递查询参数,支持后端分页逻辑,解决一次性加载海量数据卡顿问题。

3.2 具体修改内容

  1. 导入依赖:引入dio.dart与api_config.dart,关联网络配置;

  2. 类与方法定义:定义FoodApi类,新增带分页参数的静态方法getFoodList;

  3. 分页参数构造:通过queryParameters传递page和pageSize到后端接口;

  4. 日志与异常处理:打印请求地址、参数与返回数据(调试用),捕获并抛出异常(供页面层处理)。

核心代码实现(food_api.dart):

复制代码
// food_api.dart
// 先导入依赖包
import 'package:dio/dio.dart';
import '../core/http/api_config.dart'; // 对应api_config的路径

// 定义FoodApi类
class FoodApi {
  // 改为类的静态方法,并接收分页参数page/pageSize
  static Future<dynamic> getFoodList({
    required int page,
    required int pageSize,
  }) async {
    try {
      Dio dio = Dio();
      // 构造分页请求参数(传递给接口的query参数)
      final queryParams = {
        "page": page,
        "pageSize": pageSize,
      };
      print("请求接口:${ApiConfig.food_list_url},参数:$queryParams"); // 打印请求地址
      // 发起请求时携带分页参数,调用api_config中配置的接口地址
      Response response = await dio.get(
        ApiConfig.food_list_url,
        queryParameters: queryParams, // 关键:把分页参数传给后端接口
      );
      print("接口返回:${response.data}"); // 打印返回数据
      return response.data; // 提取接口返回的美食列表数据
    } catch (e) {
      print("接口请求失败:$e"); // 打印错误
      throw e;
    }
  }
}

3.3 功能作用

  1. 统一API请求入口:所有美食列表请求均通过FoodApi.getFoodList发起,便于维护;

  2. 支持分页控制:通过page和pageSize参数实现"增量加载",减少网络带宽占用与内存消耗;

  3. 调试友好:日志打印请求详情,快速定位接口问题(如参数错误、地址错误)。

4 页面层:核心逻辑实现

页面层:负责核心逻辑实现,修改文件:lib/pages/food/food_list_page.dart。

4.1 实现目标

整合"下拉刷新、上拉加载、加载提示"三大功能,通过状态管理实现UI与数据。

4.2 状态变量定义(页面层核心基础)

  1. 修改内容:新增分页控制、加载状态、控制器变量,统一管理交互逻辑。

  2. 核心代码(对应_FoodListPageState类顶部):

    class _FoodListPageState extends State<FoodListPage> {
    List<FoodModel> _foodList = [];
    final RefreshController _refreshController = RefreshController(initialRefresh: false);
    // 分页参数与加载状态
    final int _pageSize = 6; // 每页加载的数据量:6条数据
    bool _isLoading = false; // 加载锁(防止重复请求,如快速下拉/上拉)
    // 无限分页核心控制器,初始为1
    final PagingController<int, FoodModel> _pagingController = PagingController(firstPageKey: 1);
    @override
    void initState() {
    super.initState();
    _getFoodListData(isRefresh: true); // 初始化加载数据,初始化时执行「下拉刷新」逻辑(加载第1页数据)
    // 新增:绑定上拉分页监听 - 滑到底部自动触发加载下一页,核心逻辑
    _pagingController.addPageRequestListener((pageKey) {
    _fetchPage(pageKey);
    });
    }
    // ...后续方法实现
    }

  3. 功能作用:

_refreshController:控制下拉刷新的状态(开始/完成/失败);

_pagingController:管理上拉分页的页码、数据追加、加载状态(无更多/失败);

_isLoading:防止"同时触发下拉刷新与上拉加载"或"快速重复上拉"导致的重复请求。

4.3 下拉刷新逻辑实现

  1. 修改内容:实现_getFoodListData方法,绑定到SmartRefresher的onRefresh回调,触发时重置状态并重新加载第一页数据。

  2. 核心代码:

    // 获取美食数据(新增isRefresh参数,区分刷新/加载)
    Future<void> _getFoodListData({required bool isRefresh}) async { // _getFoodListData 方法
    // 加载锁:防止重复请求
    if (_isLoading) return;
    _isLoading = true;
    try {
    if(isRefresh){ // 下拉刷新时:重置页码、数据、状态
    _pagingController.refresh(); // 重置分页控制器,清空历史分页状态
    await Future.delayed(const Duration(seconds: 2)); // 下拉刷新→加载中 停留2秒
    } else {
    await Future.delayed(const Duration(seconds: 2));// 上拉加载时加延迟(延长"加载中..."显示时间)
    }
    // 调用API层分页请求,下拉刷新永远请求第1页
    final data = await FoodApi.getFoodList(
    page: 1, // 下拉刷新永远请求第1页
    pageSize: _pageSize,
    );
    if (mounted) {
    if (data is Map<String, dynamic>) { // 先判断data是否是List类型
    // 解析后端返回的Map格式数据(适配后端结构)
    final foodListModel = FoodListModel.fromJson(data);
    setState(() {
    if (isRefresh) {
    _foodList = foodListModel.foodList; // 下拉刷新:替换原有数据
    } else {
    _foodList.addAll(foodListModel.foodList); // 上拉加载:追加新数据
    }
    });
    // 新增:下拉刷新成功后,给分页控制器赋值第一页数据
    _pagingController.value = PagingState(// 更新分页控制器:设置第一页数据与下一页页码
    nextPageKey: foodListModel.foodList.length >= _pageSize ? 2 : null,
    itemList: foodListModel.foodList,
    );
    } else {
    throw Exception("接口返回数据格式错误,不是对象格式");
    }
    }
    } catch (e) {
    if (mounted) {// 加载失败:显示错误提示
    ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text("加载失败:${e.toString()}")),
    );
    }
    } finally {
    _isLoading = false;
    if (isRefresh) {// 下拉刷新成功提示
    Future.delayed(const Duration(seconds: 2), () {
    if (mounted) {
    _refreshController.refreshCompleted();// 结束下拉刷新动画(必须调用,否则刷新状态不会重置)
    ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text("美食列表加载成功")),
    );
    }
    });
    }
    }
    }

  3. 功能作用:

状态重置:下拉时重置_pagingController,避免分页状态混乱;

数据替换:重新请求第1页数据,覆盖原有列表,实现"刷新"效果。

4.4 上拉加载逻辑实现

  1. 修改内容:实现_fetchPage方法,绑定到_pagingController的分页监听,滑到底部时自动请求下一页数据。

  2. 核心代码:

    // 上拉加载更多专用方法(pageKey为当前要加载的页码)
    // 新增:infinite_scroll_pagination 专用分页请求方法
    Future<void> _fetchPage(int pageKey) async {
    if (_isLoading) return;
    _isLoading = true;
    try {
    await Future.delayed(const Duration(seconds: 2));// 延长加载提示显示
    final data = await FoodApi.getFoodList( // 调用API层,请求当前页码数据
    page: pageKey,
    pageSize: _pageSize,
    );
    if (mounted && data is Map<String, dynamic>) {
    final foodListModel = FoodListModel.fromJson(data);
    final newItems = foodListModel.foodList;
    // 判断是否为最后一页(返回数据量 < 每页大小 → 无更多数据)
    final isLastPage = newItems.length < _pageSize;
    if (isLastPage) {
    // 无更多数据:通知分页控制器停止上拉加载
    _pagingController.appendLastPage(newItems);
    } else {
    // 有更多数据,自动加载下一页,页码+1,无限上拉核心逻辑
    final nextPageKey = pageKey + 1;
    _pagingController.appendPage(newItems, nextPageKey);
    }
    }
    } catch (e) {
    // 加载失败:通知分页控制器显示失败提示
    _pagingController.error = e;
    if(mounted){
    ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text("加载失败:${e.toString()}")),
    );
    }
    } finally {
    _isLoading = false;
    }
    }

  3. 功能作用:

自动分页:滑到底部时_pagingController自动触发_fetchPage,请求下一页;

边界处理:通过isLastPage判断是否停止上拉,避免无效请求;

失败兼容:加载失败时通过_pagingController.error显示失败提示,无需手动维护失败UI。

4.5 数据加载提示实现

  1. 修改内容:通过SmartRefresher(下拉提示)与PagedChildBuilderDelegate(上拉/初始化提示)覆盖所有加载场景。

  2. 核心代码(build方法中):

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: const Text("本地美食清单"), centerTitle: true),
    body: SmartRefresher( // 下拉刷新容器(pull_to_refresh组件)
    controller: refreshController,
    // 下拉刷新:调用带isRefresh=true的请求
    onRefresh: () => getFoodListData(isRefresh: true), // 绑定下拉刷新
    // 自定义下拉头部(实现"刷新中/刷新完成"提示 + 颜色/字体优化)
    header: CustomHeader(
    builder: (context, mode) {
    String headerText = "";
    Color textColor = Colors.black87; // 初始文字颜色
    // 根据刷新状态切换文案和颜色
    if (mode == RefreshStatus.refreshing) {
    headerText = "刷新中"; // 刷新中显示"刷新中"
    textColor = Colors.blueAccent; // 加载中用橙色,更醒目
    } else if (mode == RefreshStatus.completed) {
    headerText = "刷新完成"; // 刷新完成显示"刷新完成"
    textColor = Colors.grey[600]!; // 刷新完成用绿色,更友好
    } else {
    headerText = "下拉刷新"; // 初始状态显示"下拉刷新"
    textColor = Colors.grey[600]!;
    }
    return Container(
    height: 60,
    alignment: Alignment.center,
    child: Text(headerText, // 给Text添加style,应用textColor和字体样式
    style: TextStyle(
    color: textColor, // 应用定义的文字颜色
    fontSize: 16, // 加大字号,更醒目
    fontWeight: FontWeight.w400, // 加粗字体
    ),
    ), // 显示对应的提示文本
    );
    },
    ),
    // 上拉分页列表(infinite_scroll_pagination组件)
    child: PagedListView<int, FoodModel>(
    pagingController: pagingController,
    builderDelegate: PagedChildBuilderDelegate<FoodModel>(
    // 列表项UI(复用原有方法)
    itemBuilder: (context, item, index) => buildFoodItem(item),
    // 上拉加载中提示(转圈+文字)
    newPageProgressIndicatorBuilder: (
    ) => Container(
    height: 60,
    alignment: Alignment.center,
    child: Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: const [
    CircularProgressIndicator(strokeWidth: 2),
    SizedBox(width: 8),
    Text("加载中..."),
    ],
    ),
    ),
    noMoreItemsIndicatorBuilder: (
    ) => Container( // 无更多数据提示
    height: 60,
    alignment: Alignment.center,
    child: const Text("已加载全部数据"),
    ),
    // 初始化加载失败提示
    firstPageErrorIndicatorBuilder: (
    ) => Container(
    height: 60,
    alignment: Alignment.center,
    child: const Text("加载失败,下拉重试"),
    ),
    // 上拉加载失败提示
    newPageErrorIndicatorBuilder: (
    ) => Container(
    height: 60,
    alignment: Alignment.center,
    child: const Text("加载失败,上拉重试"),
    ),
    ),
    ),
    ),
    );
    }

  3. 功能作用:

全场景覆盖:包含"下拉刷新中/完成"、"上拉加载中"、"无更多数据"、"初始化/上拉失败"5种场景,用户操作有明确反馈;

鸿蒙适配:提示UI简洁,字体大小(16号)、颜色(灰色/蓝色)符合鸿蒙设备交互规范,避免视觉杂乱。

5 测试与验证

5.1 测试环境

测试设备:OpenHarmony 模拟器(API Version 20);

测试工具:DevEco Studio 6.0.0。

5.2 功能验证

  1. 下拉刷新 + 数据加载提示:

下拉列表触发刷新,下拉拖动后首先页面顶部显示"下拉刷新"提示;触发刷新后顶部显示"刷新中"提示,等待刷新;刷新完成后顶部显示"刷新完成"提示。

  1. 上拉加载 + 数据加载提示:

上拉页面至底部显示"加载中..."提示(含转圈动画),2秒后追加新的美食数据,加载完成后列表自动滚动至新增数据区域。

6 代码提交与 AtomGit 仓库更新

结合我前面创建已有的 AtomGit 仓库(classmate-h/flutter_HmProject),更新项目的步骤如下(基于 VS Code 终端操作,确保终端路径处于项目根目录):

6.1 查看本地修改

查看本地修改(确认要提交的内容):

在 VS Code 终端执行命令,查看当前修改的文件(比如我在本文中修改的food_list_page.dart、pubspec.yaml等)。

复制代码
git status

博主通过上图显示从终端输出可以看到,git status 已经成功执行(命令正常返回了仓库状态信息)。

输出信息说明当前仓库的状态:

  1. 分支:处于main分支,且本地main分支与远程origin/main分支是 "同步的(up to date)";

  2. 存在未暂存的修改文件(如lib/main.dart、pubspec.yaml等);

  3. 存在未被 Git 跟踪的新文件 / 目录(如.vscode/、lib/api/等)。

简单来说,git status命令本身执行成功了,它只是告诉你当前仓库里有未处理的修改和新文件。

6.2 暂存本地修改

将所有修改的文件暂存到 Git 缓存区(若只想提交部分文件,可替换.为具体文件名,如git add lib/pages/food/food_list_page.dart):

复制代码
git add .

博主在执行完命令后,从上面图片显示结果界面可以判断,git add . 已经成功执行。

  1. 终端没有出现 "error" 类的错误提示,仅显示 "LF will be replaced by CRLF" 的换行符兼容警告(这是 Git 的正常提示,不是错误,不影响操作);

  2. 左侧资源管理器中,修改 / 新增的文件旁边出现了 A(新增文件)、M(修改文件)的标记,说明这些文件已经成功被暂存到 Git 缓存区。

简单说:git add . 已经完成了"暂存所有修改文件"的任务,接下来可以执行 "提交"操作了。

6.3 提交本地修改

提交本地修改(写清楚更新内容):

按照 Git 提交规范,编写清晰的 commit message(说明本次更新了什么):

复制代码
git commit -m "Initial commit: Flutter+鸿蒙跨平台工程"

博主执行完上述命令后从终端输出可以明确,git commit -m "Initial commit: Flutter+鸿蒙跨平台工程" 已经成功执行。

判断依据:

  1. 终端显示了提交的标识:[main 11f11ba] Initial commit: Flutter+鸿蒙跨平台工程(其中11f11ba是本次提交的唯一哈希值,代表提交已被 Git 记录);

  2. 输出了提交的文件变更统计:13 files changed, 1163 insertions(+), 67 deletions(-),以及具体文件的创建 / 修改状态(如create mode 100644 lib/pages/food/food_list_page.dart),说明 Git 已成功将暂存的文件提交到我的本地 Git 仓库。

6.4 拉取远程仓库最新代码

先拉取 AtomGit 上的最新代码(防止远程有更新导致推送冲突):

复制代码
git pull origin main

若提示 "Already up to date":说明远程代码和本地一致,直接进行下一步;

若出现冲突:打开冲突文件(终端会提示冲突文件路径),解决代码冲突后,重新执行git add .→git commit -m "XXXXXX",再继续。

执行完命令后,从上面图片中终端输出可以看到,git pull origin main 已经成功执行。

输出里的Already up to date.表示:本地的main分支与远程 AtomGit 仓库的main分支已经是同步的状态(没有新的更新需要拉取)。

这是正常的执行结果,说明当前本地代码和远程仓库的代码是一致的,接下来可以执行git push origin main将本地的新提交推送到远程仓库。

6.5 推送更新到 AtomGit 仓库

将本地最新提交推送到 AtomGit 的main分支:

复制代码
git push origin main

从终端输出可以明确,git push origin main 已经成功执行。

判断依据:

  1. 终端完成了"枚举对象、计数、压缩、写入"等推送流程,无任何错误提示;

  2. 最后输出了推送结果:To https://gitcode.com/classmate-h/flutter_HmProject.git + 提交哈希的同步记录(19b4ef5...11f11ba main -> main);

  3. 出现了[PASSED]标识,说明远程仓库的钩子校验也已通过。

现在可以刷新 AtomGit 仓库的网页,就能看到最新的提交记录和更新时间了。

6.6 验证推送结果

打开 AtomGit 仓库页面(在 AtomGit 创建的个人公开仓库),刷新页面:

确认仓库的"提交记录"中出现刚写的commit message;

查看lib/pages/food/food_list_page.dart等文件,确认最新代码已同步。

刷新后的页面也能看到最新的提交记录和更新时间。

全部步骤命令:

复制代码
# 查看修改
git status
# 暂存修改
git add .
# 提交修改(替换为你的更新说明)
git commit -m "XXXXXX"
# 拉取远程代码
git pull origin main
# 推送更新
git push origin main

7 总结与展望

7.1 总结

  1. 功能落地:完成开源鸿蒙Flutter列表的"下拉刷新、上拉加载、数据加载提示"三大核心能力,解决数据加载卡顿、用户操作无反馈等问题;

  2. 验证通过:在DevEco Studio(API 20模拟器)上完成场景验证,功能兼容、交互流畅;

  3. 代码规范:遵循开源鸿蒙编码标准与Git提交规范,代码可维护、可复现,推送至本人AtomGit公开仓库后可直接拉取运行。

7.2 扩展方向

  1. 通过新增底部选项卡及完善对应页面实现,丰富应用交互维度与服务能力,比如新增美食分类、收藏、搜索功能;

  2. 为应用全面集成动效能力,覆盖页面转场、组件交互、数据加载等核心场景,提升应用视觉体验与交互流畅度。

最后,

欢迎加入开源鸿蒙跨平台社区:

https://openharmonycrossplatform.csdn.net

相关推荐
猛扇赵四那边好嘴.1 小时前
Flutter 框架跨平台鸿蒙开发 - 诗词鉴赏应用开发教程
flutter·华为·harmonyos
IT陈图图2 小时前
跨端之旅:Flutter × OpenHarmony 构建旅行记录应用的搜索栏
flutter·开源·鸿蒙·openharmony
—Qeyser2 小时前
Flutter组件 - BottomNavigationBar 底部导航栏
开发语言·javascript·flutter
时光慢煮2 小时前
行旅迹 · 基于 Flutter × OpenHarmony 的旅行记录应用— 构建高体验旅行记录列表视图的跨端实践
flutter·华为·开源·openharmony
IT陈图图2 小时前
Flutter × OpenHarmony 跨端汇率转换:常用货币对构建与实现解析
flutter·鸿蒙·openharmony
时光慢煮2 小时前
行走的记忆卡片:基于 Flutter × OpenHarmony 的旅行记录应用实践——单个旅行记录卡片构建详解
flutter·华为·开源·openharmony
大雷神2 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地---第1篇:项目初始化与环境搭建
华为·harmonyos
小白阿龙2 小时前
鸿蒙+flutter 跨平台开发——智力迷宫挑战的实现
flutter·游戏·华为·harmonyos·鸿蒙
世人万千丶2 小时前
Day 5: Flutter 框架 SQLite 数据库进阶 - 在跨端应用中构建结构化数据中心
数据库·学习·flutter·sqlite·harmonyos·鸿蒙·鸿蒙系统