在这里插入图片描述

✨“俺はモンキー・D・ルフィ。海贼王になる男だ!”

在这里插入图片描述


在这里插入图片描述

本文将带你一步步完成Flutter鸿蒙应用中分类模块的开发,实现从API数据获取到最终UI展示的全流程。

开发准备

在开始编码之前,请确保已完成以下准备工作:

  • 已配置好Flutter鸿蒙开发环境
  • 安装Dio网络请求

一、项目架构设计

1.1 整体开发思路

分类功能的开发可以拆解为以下几个核心步骤:

API接口定义 → 数据模型构建 → 网络请求封装 → UI组件开发 → 数据联调

1.2 技术选型说明

技术组件 用途 选择理由
Dio HTTP网络请求 功能强大、易于使用、支持拦截器
Factory模式 数据模型转换 优雅处理JSON到Dart对象的转换
ListView.builder 列表渲染 高性能的懒加载渲染方案
CustomScrollView 滚动容器 支持Sliver家族组件的混合滚动

二、数据模型层实现

2.1 API接口配置

首先,我们需要将项目中用到的常量和接口地址进行统一管理。

文件路径: lib/constants/index.dart

/// 全局配置常量
class AppConfig {
  // API基础地址
  static const String apiBaseUrl = "https://meikou-api.itheima.net/";

  // 网络请求超时时间(秒)
  static const int requestTimeout = 10;

  // 业务成功状态码
  static const String successCode = "1";
}

/// API接口地址常量
class ApiEndpoints {
  // 首页轮播图接口
  static const String bannerList = "/home/banner";

  // 首页分类接口
  static const String categoryList = "/home/category/head";
}

2.2 分类数据模型

分类接口返回的是嵌套的JSON结构,包含父级分类和子级分类。我们需要设计一个支持递归结构的数据模型。

文件路径: lib/viewmodels/home.dart

/// 轮播图数据模型
class BannerItem {
  final String id;
  final String imgUrl;

  BannerItem({
    required this.id,
    required this.imgUrl,
  });

  /// JSON转对象工厂方法
  factory BannerItem.fromJson(Map<String, dynamic> json) {
    return BannerItem(
      id: json["id"] ?? "",
      imgUrl: json["imgUrl"] ?? "",
    );
  }
}

/// 分类数据模型(支持树形结构)
class CategoryItem {
  final String id;
  final String name;
  final String? picture;
  final List<CategoryItem>? children;

  CategoryItem({
    required this.id,
    required this.name,
    this.picture,
    this.children,
  });

  /// 从JSON创建分类对象(支持递归处理子分类)
  factory CategoryItem.fromJson(Map<String, dynamic> json) {
    // 处理子分类列表
    List<CategoryItem>? parseChildren(dynamic childrenData) {
      if (childrenData == null) return null;
      return (childrenData as List)
          .map((item) => CategoryItem.fromJson(item as Map<String, dynamic>))
          .toList();
    }

    return CategoryItem(
      id: json["id"] ?? "",
      name: json["name"] ?? "",
      picture: json["picture"],
      children: parseChildren(json["children"]),
    );
  }
}

代码解析

上述数据模型的核心要点:

  1. 使用factory关键字:声明工厂构造函数,用于实现对象创建逻辑
  2. 空安全处理:使用??运算符提供默认值,避免空指针异常
  3. 递归结构children字段类型为List<CategoryItem>?,支持多层嵌套
  4. 类型转换:使用as关键字进行显式类型转换

三、网络层封装

3.1 Dio基础配置

文件路径: lib/utils/http_client.dart

import 'package:dio/dio.dart';
import '../constants/index.dart';

class HttpClient {
  static final HttpClient _instance = HttpClient._internal();
  factory HttpClient() => _instance;

  late Dio _dio;

  HttpClient._internal() {
    _dio = Dio(BaseOptions(
      baseUrl: AppConfig.apiBaseUrl,
      connectTimeout: Duration(seconds: AppConfig.requestTimeout),
      receiveTimeout: Duration(seconds: AppConfig.requestTimeout),
    ));

    // 添加拦截器
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        print('请求: ${options.method} ${options.uri}');
        return handler.next(options);
      },
      onResponse: (response, handler) {
        print('响应: ${response.statusCode}');
        return handler.next(response);
      },
      onError: (error, handler) {
        print('错误: ${error.message}');
        return handler.next(error);
      },
    ));
  }

  Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) {
    return _dio.get(path, queryParameters: queryParameters);
  }
}

3.2 API服务层

文件路径: lib/api/home.dart

import 'package:qing_mall/viewmodels/home.dart';
import 'package:qing_mall/utils/http_client.dart';
import 'dart:convert';

/// 获取轮播图列表
Future<List<BannerItem>> getBannerListAPI() async {
  try {
    final response = await HttpClient().get(ApiEndpoints.bannerList);

    if (response.data['code'] == AppConfig.successCode) {
      final List<dynamic> dataList = response.data['result'];
      return dataList.map((e) => BannerItem.fromJson(e)).toList();
    }
    return [];
  } catch (e) {
    print('获取轮播图失败: $e');
    return [];
  }
}

/// 获取分类列表数据
///
/// 返回值:分类对象列表,失败时返回空列表
Future<List<CategoryItem>> getCategoryListAPI() async {
  try {
    final response = await HttpClient().get(ApiEndpoints.categoryList);

    // 判断业务状态码
    if (response.data['code'] == AppConfig.successCode) {
      final List<dynamic> dataList = response.data['result'];
      // 批量转换为对象列表
      return dataList
          .map((item) => CategoryItem.fromJson(item as Map<String, dynamic>))
          .toList();
    }
    return [];
  } catch (e) {
    print('获取分类列表异常: $e');
    return [];
  }
}

四、UI组件开发

4.1 首页数据管理

文件路径: lib/pages/Home/index.dart

import 'package:flutter/cupertino.dart';
import 'package:qing_mall/api/home.dart';
import 'package:qing_mall/components/Home/HmCategory.dart';
import 'package:qing_mall/components/Home/HmHot.dart';
import 'package:qing_mall/components/Home/HmMoreList.dart';
import 'package:qing_mall/components/Home/HmSlider.dart';
import 'package:qing_mall/components/Home/HmSuggestion.dart';
import 'package:qing_mall/viewmodels/home.dart';

class HomeView extends StatefulWidget {
  const HomeView({super.key});

  
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {
  // ========== 数据状态管理 ==========

  /// 分类列表数据
  List<CategoryItem> _categoryList = [];

  /// 轮播图列表数据
  List<BannerItem> _bannerList = [];

  // ========== 生命周期方法 ==========

  
  void initState() {
    super.initState();
    _loadInitialData();
  }

  // ========== 数据加载方法 ==========

  /// 加载首页初始数据
  void _loadInitialData() {
    _fetchBannerData();
    _fetchCategoryData();
  }

  /// 获取轮播图数据
  void _fetchBannerData() async {
    final result = await getBannerListAPI();
    if (mounted) {
      setState(() {
        _bannerList = result;
      });
    }
  }

  /// 获取分类数据
  void _fetchCategoryData() async {
    final result = await getCategoryListAPI();
    if (mounted) {
      setState(() {
        _categoryList = result;
      });
    }
  }

  // ========== UI构建方法 ==========

  /// 构建滚动视图的子组件列表
  List<Widget> _buildScrollChildren() {
    return [
      // 轮播图区域
      SliverToBoxAdapter(child: HmSlider(bannerList: _bannerList)),

      // 间距
      SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 分类导航区域
      SliverToBoxAdapter(
        child: HmCategory(categoryList: _categoryList),
      ),

      // 间距
      SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 推荐搜索词
      SliverToBoxAdapter(child: HmSuggestion()),
      SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 热门商品(双列布局)
      SliverToBoxAdapter(
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 10),
          child: Row(
            children: [
              Expanded(child: HmHot()),
              SizedBox(width: 10),
              Expanded(child: HmHot()),
            ],
          ),
        ),
      ),
      SliverToBoxAdapter(child: SizedBox(height: 10)),

      // 猜你喜欢无限列表
      HmMorelist(),
    ];
  }

  
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: _buildScrollChildren(),
    );
  }
}

4.2 分类组件实现

文件路径: lib/components/Home/HmCategory.dart

初始版本

先创建一个基础的分类组件框架:

import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/home.dart';

class HmCategory extends StatefulWidget {
  final List<CategoryItem> categoryList;

  const HmCategory({
    super.key,
    required this.categoryList,
  });

  
  State<HmCategory> createState() => _HmCategoryState();
}

class _HmCategoryState extends State<HmCategory> {
  
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: widget.categoryList.length,
        itemBuilder: (BuildContext context, int index) {
          final category = widget.categoryList[index];

          return Container(
            width: 80,
            height: 100,
            margin: EdgeInsets.symmetric(horizontal: 10),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image.network(
                  category.picture ?? "",
                  width: 40,
                  height: 40,
                  errorBuilder: (context, error, stackTrace) {
                    return Icon(Icons.category, size: 40);
                  },
                ),
                SizedBox(height: 8),
                Text(
                  category.name,
                  style: TextStyle(fontSize: 12),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
优化版本

对UI进行美化处理:

import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/home.dart';

class HmCategory extends StatefulWidget {
  final List<CategoryItem> categoryList;

  const HmCategory({
    super.key,
    required this.categoryList,
  });

  
  State<HmCategory> createState() => _HmCategoryState();
}

class _HmCategoryState extends State<HmCategory> {
  
  Widget build(BuildContext context) {
    return SizedBox(
      height: 110,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        padding: EdgeInsets.symmetric(horizontal: 16),
        itemCount: widget.categoryList.length,
        itemBuilder: (BuildContext context, int index) {
          final category = widget.categoryList[index];

          return _buildCategoryItem(category);
        },
      ),
    );
  }

  /// 构建单个分类项
  Widget _buildCategoryItem(CategoryItem category) {
    return Container(
      width: 75,
      margin: EdgeInsets.only(right: 12),
      decoration: BoxDecoration(
        color: Color(0xFFF7F8FA),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 分类图标
          Container(
            width: 45,
            height: 45,
            decoration: BoxDecoration(
              color: Colors.white,
              shape: BoxShape.circle,
            ),
            child: ClipOval(
              child: Image.network(
                category.picture ?? "",
                width: 45,
                height: 45,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Icon(
                    Icons.image_not_supported,
                    size: 24,
                    color: Colors.grey[400],
                  );
                },
              ),
            ),
          ),

          SizedBox(height: 8),

          // 分类名称
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 4),
            child: Text(
              category.name,
              style: TextStyle(
                fontSize: 12,
                color: Color(0xFF333333),
                fontWeight: FontWeight.w500,
              ),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              textAlign: TextAlign.center,
            ),
          ),
        ],
      ),
    );
  }
}

在这里插入图片描述


五、开发技巧与注意事项

5.1 常见问题处理

问题1:网络图片加载失败

Image.network(
  url,
  errorBuilder: (context, error, stackTrace) {
    return Placeholder(); // 显示占位图
  },
)

问题2:ListView高度设置问题

// ListView不能直接设置高度,需要包裹SizedBox
SizedBox(
  height: 100,
  child: ListView(...),
)

问题3:状态更新后UI不刷新

// 确保在setState中更新数据
setState(() {
  _categoryList = newData;
});

5.2 性能优化建议

  1. 图片缓存:使用cached_network_image库缓存网络图片
  2. 列表优化:使用ListView.builder而非ListView构造大量子项
  3. 防抖处理:网络请求时添加防抖,避免重复调用

六、项目提交与版本管理

6.1 代码提交

开发完成后,使用Git进行版本管理:

# 添加所有变更文件
git add .

# 提交代码
git commit -m "feat: 完成分类数据获取与UI渲染功能

- 新增CategoryItem数据模型,支持树形结构
- 实现分类API接口调用
- 开发HmCategory分类组件
- 优化分类UI展示效果"

# 推送到远程仓库
git push

七、总结与展望

7.1 核心知识点回顾

本文完成了Flutter鸿蒙应用中分类模块的开发,主要涉及以下技术点:

  1. 数据建模:使用Factory模式处理JSON到Dart对象的转换
  2. 网络请求:Dio库的封装与使用
  3. 状态管理:通过setState更新UI状态
  4. 组件开发:ListView.builder实现横向滚动列表
  5. UI设计:Container、Decoration等实现圆角卡片样式

7.2 后续优化方向

优化项 说明
点击交互 添加分类项点击事件,跳转到对应商品列表
下拉刷新 实现分类数据的下拉刷新功能
骨架屏 数据加载时显示骨架屏,提升体验
动画效果 添加分类项的点击和滚动动画
数据缓存 使用本地缓存减少网络请求

7.3 学习资源推荐


结语

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐