开源鸿蒙跨平台训练营DAY4~DAY6:列表清单实现上拉加载、下拉刷新及数据加载提示
✅ 猫咪数据模型定义✅ 网络API请求封装✅ 列表基础展示✅ 后端Mock数据服务✅ 下拉刷新功能✅ 上拉加载更多分页✅ 图片异步加载与错误处理数据格式一致性:前后端数据格式必须匹配,特别是分页接口返回的对象结构网络请求封装:使用Dio封装API调用,便于统一错误处理状态管理:使用PagingController管理分页状态,避免手动维护页码图片加载优化:添加loading和error状态,提升用
概述
DAY4~DAY6准备做的项目是Flutter猫咪健康指南,本项目是一个Flutter移动应用,展示不同品种猫咪的健康信息,包括活动水平、梳理需求、健康风险等数据。核心功能包括猫咪列表展示、分页加载、下拉刷新、上拉加载更多、图片异步加载等。因为想要比较好的效果,所以在做这篇的时候重新做了列表展示页面。
前提准备
技术栈
- 前端:Flutter 3.x + Dart
- 后端:Node.js + Express 网络请求:Dio
- 分页组件:infinite_scroll_pagination
- 刷新组件:pull_to_refresh
- 开发工具:AndroidStudio
开发环境准备
确保已安装:
- Flutter SDK (3.0+)
- Node.js (14+)
- Android Studio
- 模拟器或真机
一、项目初始化与基础配置
1.1 创建Flutter项目
flutter create mao
cd mao
1.2 添加依赖
在 pubspec.yaml中添加:
dependencies:
flutter:
sdk: flutter
dio: ^5.0.0
pull_to_refresh: ^2.0.0
infinite_scroll_pagination: ^4.0.0
运行 flutter pub get安装依赖。
二、数据模型与API层设计
2.1 创建猫咪数据模型
lib/model/cat_model.dart:
class CatModel {
final int id;
final String name;
final String desc;
final String image;
final List<String> healthRisks;
final String groomingNeeds;
final String activityLevel;
final String lifeExpectancy;
final List<String> suitableFor;
final String difficultyLevel;
CatModel({
required this.id,
required this.name,
required this.desc,
required this.image,
required this.healthRisks,
required this.groomingNeeds,
required this.activityLevel,
required this.lifeExpectancy,
required this.suitableFor,
required this.difficultyLevel,
});
factory CatModel.fromJson(Map<String, dynamic> json) {
return CatModel(
id: json['id'],
name: json['name'],
desc: json['desc'],
image: json['image'],
healthRisks: List<String>.from(json['healthRisks']),
groomingNeeds: json['groomingNeeds'],
activityLevel: json['activityLevel'],
lifeExpectancy: json['lifeExpectancy'],
suitableFor: List<String>.from(json['suitableFor']),
difficultyLevel: json['difficultyLevel'],
);
}
}
2.2 创建API服务类
lib/core/cat_api.dart:
import 'package:dio/dio.dart';
import '../model/cat_model.dart';
class CatApi {
static Future<List<CatModel>> getCatList({
required int page,
required int pageSize,
}) async {
try {
Dio dio = Dio();
final response = await dio.get(
'http://10.0.2.2:5000/api/cats/list',
queryParameters: {
"page": page,
"pageSize": pageSize,
},
);
// 适配两种数据格式:数组或包含catList的对象
if (response.data is List) {
return (response.data as List)
.map((json) => CatModel.fromJson(json))
.toList();
} else if (response.data is Map<String, dynamic>) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('catList') && data['catList'] is List) {
final List<dynamic> catListJson = data['catList'];
return catListJson.map((json) => CatModel.fromJson(json)).toList();
}
}
throw Exception('接口返回数据格式错误');
} catch (e) {
rethrow;
}
}
}
三、猫咪列表页面基础实现
3.1 基础列表页面结构
lib/pages/cat/cat_list_page.dart初始版本:
import 'package:flutter/material.dart';
import '../../model/cat_model.dart';
import '../../core/cat_api.dart';
class CatListPage extends StatefulWidget {
const CatListPage({super.key});
State<CatListPage> createState() => _CatListPageState();
}
class _CatListPageState extends State<CatListPage> {
List<CatModel> _catList = [];
bool _isLoading = false;
void initState() {
super.initState();
_getCatListData();
}
Future<void> _getCatListData() async {
setState(() => _isLoading = true);
try {
final cats = await CatApi.getCatList(page: 1, pageSize: 6);
setState(() => _catList = cats);
} catch (e) {
// 错误处理
} finally {
setState(() => _isLoading = false);
}
}
Widget _buildCatItem(CatModel cat) {
return ListTile(
leading: Image.network(
'http://10.0.2.2:5000${cat.image}',
width: 50,
height: 50,
fit: BoxFit.cover,
),
title: Text(cat.name),
subtitle: Text(cat.desc),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('猫咪健康指南')),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _catList.length,
itemBuilder: (context, index) => _buildCatItem(_catList[index]),
),
);
}
}
四、后端API服务搭建
4.1 创建Node.js后端项目
在项目根目录创建 cat-local-api文件夹:
mkdir cat-local-api
cd cat-local-api
npm init -y
npm install express cors
初始化 Node.js 项目:
npm init -y
安装 Express 框架:
npm install express
安装成功会生成这三个
4.2 images 文件夹
在项目根目录创建 public/images 文件夹,放入所有需要的图片。要统一格式和命名,最开始列表我用了6张,后面增加功能的时候用了12张
4.2 创建server.js
cat-local-api/server.js:
const express = require('express');
const cors = require('cors');
const app = express();
const port = 5000;
// 配置静态资源目录
app.use(express.static('public'));
app.use(cors());
// 模拟猫咪数据(完整12个品种数据)
const mockCatData = [...]; // 此处省略具体数据
// 猫咪列表接口
app.get('/api/cats/list', (req, res) => {
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.pageSize) || 6;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedCatList = [];
for (let i = startIndex; i < endIndex; i++) {
const loopIndex = i % mockCatData.length;
paginatedCatList.push(mockCatData[loopIndex]);
}
// 返回包含catList字段的对象
res.json({
catList: paginatedCatList,
total: mockCatData.length,
currentPage: page,
pageSize: pageSize,
hasMore: endIndex < mockCatData.length * 2
});
});
app.listen(port, () => {
console.log(`API服务启动: http://localhost:${port}`);
});
4.3 启动后端服务
node server.js
这里可以看到类似这样的页面(至少有列表接口的),可以按Ctrl点击,跳转查看链接,这个不要关掉!!!

链接的展示效果类似这种,相当于验证成功
这个时候运行应该可以看到列表画面了
我这里是没有设置好文字和图片的排版导致了超出,然后图片因为下载的时候只命名了没有统一格式所有没有,简单的修改一下就可以了
- 如果改完了还没有图片,可以试着重启后端看一下,就是关闭上一次的node server.js,再开一个新的cmd再次运行这个node server.js

这是修改好的
五、下拉刷新功能实现
5.1 引入pull_to_refresh
在列表页面中添加下拉刷新功能:
import 'package:pull_to_refresh/pull_to_refresh.dart';
class _CatListPageState extends State<CatListPage> {
final RefreshController _refreshController = RefreshController();
// 下拉刷新回调
Future<void> _onRefresh() async {
await _getCatListData(isRefresh: true);
_refreshController.refreshCompleted();
}
Widget build(BuildContext context) {
return Scaffold(
body: SmartRefresher(
controller: _refreshController,
enablePullDown: true,
onRefresh: _onRefresh,
child: ListView.builder(
// ... 列表构建逻辑
),
),
);
}
}
六、上拉加载更多功能实现
6.1 引入infinite_scroll_pagination
使用PagingController实现分页加载:
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class _CatListPageState extends State<CatListPage> {
final PagingController<int, CatModel> _pagingController =
PagingController(firstPageKey: 1);
final int _pageSize = 6;
void initState() {
super.initState();
_pagingController.addPageRequestListener((pageKey) {
_fetchPage(pageKey);
});
}
Future<void> _fetchPage(int pageKey) async {
try {
final newItems = await CatApi.getCatList(
page: pageKey,
pageSize: _pageSize,
);
final isLastPage = newItems.length < _pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + 1;
_pagingController.appendPage(newItems, nextPageKey);
}
} catch (error) {
_pagingController.error = error;
}
}
Widget build(BuildContext context) {
return Scaffold(
body: PagedListView<int, CatModel>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<CatModel>(
itemBuilder: (context, cat, index) => _buildCatItem(cat),
// 各种状态指示器
),
),
);
}
}
七、运行尝试和处理优化
7.1 注意
这里也需要重启后端,再次运行node server.js
因为已经设置了分页所以第二页需要在后面添加?page=2才可以跳转
这是第一页,也就是链接直接跳转
这是第二页
7.2 代码说明
- 完整项目代码:由于篇幅限制,增添功能的完整代码已省略部分重复内容。
- 因为效果偏差,还进行了一些优化和改正调整,包括图片加载错误处理,状态管理错误预防,内存泄漏与性能优化和用户体验优化
八、最终效果与总结
8.1 项目结构

8.2 核心功能实现
✅ 猫咪数据模型定义
✅ 网络API请求封装
✅ 列表基础展示
✅ 后端Mock数据服务
✅ 下拉刷新功能
✅ 上拉加载更多分页
✅ 图片异步加载与错误处理
8.3 开发要点总结
- 数据格式一致性:前后端数据格式必须匹配,特别是分页接口返回的对象结构
- 网络请求封装:使用Dio封装API调用,便于统一错误处理
- 状态管理:使用PagingController管理分页状态,避免手动维护页码
- 图片加载优化:添加loading和error状态,提升用户体验
8.4 运行效果
- 启动后端服务后,运行应用,可以看到:
猫咪列表分页加载

- 下拉刷新更新数据


- 上拉自动加载下一页

- 图片异步加载,显示加载状态和错误占位
- 彩色标签展示猫咪属性,不同等级不同颜色


总结
通过这个项目,我们完整实现了Flutter列表页面的核心功能,包括数据获取、分页加载、刷新交互等,为后续功能扩展打下了坚实基础。
欢迎加入开源鸿蒙跨平台社区
https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)