概述

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

Logo

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

更多推荐