Flutter 轮播图数据获取完整指南(从API到UI展示)

前言

在前面的文章中,我们已经实现了轮播图的UI组件。本篇文章将介绍如何从服务器获取真实的轮播图数据,并将数据展示到页面上。

本文重点:

  • ✅ API接口调用流程
  • ✅ JSON数据转换
  • ✅ 数据传递到组件
  • ✅ 网络图片加载
  • ✅ 加载和错误状态处理

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

一、数据流程

┌─────────────┐
│   首页组件   │
│  initState  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│调用 API    │
│getBannerListAPI()
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ Dio 发送请求│
│ /home/banner│
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 服务器返回  │
│  JSON 数据  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│fromJSON转换│
│→ List<BannerItem>
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ setState   │
│ 更新 UI    │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│轮播图显示  │
│网络图片    │
└─────────────┘

二、API接口信息

项目
接口地址 https://meikou-api.itheima.net/home/banner
请求方式 GET
超时时间 10秒

返回数据示例:

{
  "code": "1",
  "message": "success",
  "result": [
    {
      "id": "1",
      "imgUrl": "https://image.example.com/banner1.jpg"
    },
    {
      "id": "2",
      "imgUrl": "https://image.example.com/banner2.jpg"
    }
  ]
}

三、数据模型定义

文件: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']?.toString() ?? '',
      imgUrl: json['imgUrl'] ?? '',
    );
  }
}

四、API接口封装

文件:lib/api/home.dart

import 'package:harmonyos_day_four/utils/DioRequest.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';
import 'package:harmonyos_day_four/constants/index.dart';

/// 获取轮播图列表数据
Future<List<BannerItem>> getBannerListAPI() async {
  // 调用 Dio 请求
  final result = await dioRequest.get(HttpConstants.BANNER_LIST);

  // 转换为 List
  final list = result as List;

  // 遍历转换每个 item
  return list.map((item) {
    return BannerItem.fromJSON(item as Map<String, dynamic>);
  }).toList();
}

封装说明:

  • 调用 dioRequest.get() 发送请求
  • 返回的是 result 数组
  • 使用 map 遍历转换每个对象
  • 使用 fromJSON 工厂函数转换

五、页面中调用API

文件:lib/pages/home/index.dart

import 'package:flutter/material.dart';
import 'package:harmonyos_day_four/api/home.dart';
import 'package:harmonyos_day_four/components/Home/HmSlider.dart';
import 'package:harmonyos_day_four/viewmodels/home.dart';

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

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

class _HomeViewState extends State<HomeView> {
  // 轮播图数据
  List<BannerItem> _bannerList = [];

  
  void initState() {
    super.initState();
    // 页面初始化时获取数据
    _getBannerList();
  }

  /// 获取轮播图数据
  void _getBannerList() async {
    try {
      // 调用 API
      _bannerList = await getBannerListAPI();

      // 更新 UI
      setState(() {});
    } catch (e) {
      // 打印错误信息
      print('获取轮播图数据失败: $e');
    }
  }

  
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        // 轮播图组件
        SliverToBoxAdapter(
          child: HmSlider(bannerList: _bannerList),
        ),
        const SliverToBoxAdapter(child: SizedBox(height: 10)),
        // 其他组件...
      ],
    );
  }
}

六、轮播图组件支持网络图片

文件:lib/components/Home/HmSlider.dart

class HmSlider extends StatefulWidget {
  // 接收轮播图数据参数
  final List<BannerItem> bannerList;

  const HmSlider({super.key, required this.bannerList});

  // ... 其他代码

  
  Widget build(BuildContext context) {
    final double screenWidth = MediaQuery.of(context).size.width;

    // 如果没有数据,显示加载中
    if (bannerList.isEmpty) {
      return SizedBox(
        height: 300,
        child: Container(
          color: Colors.grey[300],
          child: const Center(
            child: CircularProgressIndicator(),
          ),
        ),
      );
    }

    return SizedBox(
      height: 300,
      child: PageView.builder(
        controller: _pageController,
        itemCount: bannerList.length,
        itemBuilder: (context, index) {
          // 使用 Image.network 加载网络图片
          return Image.network(
            bannerList[index].imgUrl,
            width: screenWidth,
            height: 300,
            fit: BoxFit.cover,
            // 加载中显示进度
            loadingBuilder: (context, child, loadingProgress) {
              if (loadingProgress == null) return child;
              return Container(
                color: Colors.grey[200],
                child: Center(
                  child: CircularProgressIndicator(
                    value: loadingProgress.expectedTotalBytes != null
                        ? loadingProgress.cumulativeBytesLoaded /
                            loadingProgress.expectedTotalBytes!
                        : null,
                  ),
                ),
              );
            },
            // 加载失败显示占位图
            errorBuilder: (context, error, stackTrace) {
              return Container(
                color: Colors.grey[300],
                child: const Center(
                  child: Icon(Icons.broken_image, size: 50),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

七、数据转换过程详解

7.1 JSON 转换流程

服务器返回的 JSON:
{
  "code": "1",
  "result": [
    {"id": "1", "imgUrl": "https://xxx.com/1.jpg"}
  ]
        ↓
DioRequest 处理后:
提取 result 字段 → [{"id": "1", "imgUrl": "https://xxx.com/1.jpg"}]
        ↓
List.map 遍历:
for each item in list:
  BannerItem.fromJSON(item)
        ↓
最终数据:
List<BannerItem>[
  BannerItem(id: "1", imgUrl: "https://xxx.com/1.jpg"),
  ...
]

7.2 fromJSON 工厂函数

// 原始 JSON
{"id": "1", "imgUrl": "https://xxx.com/1.jpg"}// fromJSON 处理
BannerItem(
  id: json['id']?.toString() ?? '',      // "1"
  imgUrl: json['imgUrl'] ?? ''          // "https://xxx.com/1.jpg"
)

为什么使用 ?? 空合并运算符?

  • 防止服务器返回 null
  • 提供默认空字符串
  • 避免空指针异常

八、常见问题与解决方案

问题1:轮播图不显示任何内容

原因排查:

  1. 检查数据是否为空
print('轮播图数据: $_bannerList');  // 调试打印
  1. 检查 API 是否被调用
void _getBannerList() async {
  print('开始获取数据...');
  _bannerList = await getBannerListAPI();
  print('获取到 ${_bannerList.length} 条数据');
  setState(() {});
}
  1. 检查网络请求是否成功
try {
  _bannerList = await getBannerListAPI();
  print('API 调用成功');
} catch (e) {
  print('API 调用失败: $e');
}

问题2:网络图片显示空白

原因分析:

  • 网络图片链接无效
  • HarmonyOS 网络权限配置问题
  • 图片链接需要 HTTPS

解决方案:

  1. 使用 errorBuilder 查看错误
Image.network(
  url,
  errorBuilder: (context, error, stackTrace) {
    print('图片加载失败: $error');
    return Container(
      color: Colors.grey[300],
      child: const Icon(Icons.broken_image),
    );
  },
)
  1. 检查图片链接
// 确保图片链接是 HTTPS
imgUrl: json['imgUrl'] ?? '',

问题3:JSON 解析报错

错误信息:

type 'List<dynamic>' is not a subtype of type 'List<String>'

原因分析:
服务器返回的数据结构与预期不符。

解决方案:

// 使用 as List 转换
final list = result as List;

// 检查每个 item 的类型
list.map((item) {
  print('item type: ${item.runtimeType}');
  return BannerItem.fromJSON(item as Map<String, dynamic>);
}).toList();

问题4:setState 报错 “setState() called after dispose()”

原因分析:
异步请求完成后,页面已经销毁。

解决方案:

void _getBannerList() async {
  try {
    _bannerList = await getBannerListAPI();

    // 检查组件是否还存在
    if (!mounted) return;

    setState(() {});
  } catch (e) {
    print('获取数据失败: $e');
  }
}

问题5:数据格式不匹配

问题描述:
服务器返回的字段名与代码不一致。

解决方案:

factory BannerItem.fromJSON(Map<String, dynamic> json) {
  return BannerItem(
    id: json['id']?.toString() ?? json['bannerId']?.toString() ?? '',
    imgUrl: json['imgUrl'] ?? json['image'] ?? '',
  );
}

九、完整数据流程示例

// 1. 页面初始化

void initState() {
  super.initState();
  _getBannerList();  // 开始获取数据
}

// 2. 调用 API
void _getBannerList() async {
  try {
    // 3. 发起网络请求
    _bannerList = await getBannerListAPI();

    // 4. 更新 UI
    if (!mounted) return;
    setState(() {});

    // 5. PageView 重建,显示网络图片
  } catch (e) {
    print('获取数据失败: $e');
  }
}

// 6. 轮播图组件显示
PageView.builder(
  itemBuilder: (context, index) {
    return Image.network(
      _bannerList[index].imgUrl,  // 7. 加载网络图片
      fit: BoxFit.cover,
    );
  },
)

十、项目结构

lib/
├── api/
│   └── home.dart           # getBannerListAPI()
├── constants/
│   └── index.dart          # BANNER_LIST 常量
├── utils/
│   └── DioRequest.dart     # 网络请求工具
├── viewmodels/
│   └── home.dart           # BannerItem.fromJSON()
├── components/Home/
│   └── HmSlider.dart       # 轮播图组件
└── pages/home/
    └── index.dart          # _getBannerList()

十一、总结

本文实现了从 API 获取轮播图数据的完整流程:

步骤 内容
1 页面初始化调用 API
2 Dio 发送网络请求
3 服务器返回 JSON 数据
4 使用 fromJSON 转换数据
5 setState 更新 UI
6 Image.network 加载网络图片

关键要点:

  1. API 调用放在 initState
  2. 使用 fromJSON 工厂函数转换数据
  3. 使用 mounted 检查防止内存泄漏
  4. 使用 loadingBuildererrorBuilder 处理图片加载

后续优化:

  • 添加刷新功能
  • 添加本地缓存
  • 添加加载骨架屏
  • 支持数据刷新

十二、参考资料


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

Logo

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

更多推荐