Flutter中如何将API返回数据绑定到分类柱状图?
没问题,我来帮你把API里的宠物营养数据绑定到你的自定义柱状图组件上,分几步来做:
如何将宠物API数据绑定到自定义柱状图
1. 先定义数据模型类(解析JSON更顺手)
API返回的JSON结构比较复杂,我们先把它转成Dart对象,避免直接操作Map导致的麻烦:
// 对应每日膳食占比的子项模型 class AllowanceDetail { final String name; final bool bones; final int percentage; AllowanceDetail({required this.name, required this.bones, required this.percentage}); // 从JSON转成对象的工厂方法 factory AllowanceDetail.fromJson(Map<String, dynamic> json) { return AllowanceDetail( name: json['name'], bones: json['bones'], percentage: json['percentage'], ); } } // 对应营养成分的子项模型 class NutrientDetail { final String name; final double amount; final String unit; final String imperialAmount; NutrientDetail({required this.name, required this.amount, required this.unit, required this.imperialAmount}); factory NutrientDetail.fromJson(Map<String, dynamic> json) { return NutrientDetail( name: json['name'], amount: json['amount'].toDouble(), unit: json['unit'], imperialAmount: json['imperial_amount'], ); } } // 整体的宠物数据模型 class PetData { final List<AllowanceDetail> dailyAllowance; final List<NutrientDetail> nutrientGuidelines; PetData({required this.dailyAllowance, required this.nutrientGuidelines}); factory PetData.fromJson(Map<String, dynamic> json) { // 解析膳食占比列表 var allowanceJsonList = json['daily_allowance_guidelines_details'] as List; List<AllowanceDetail> allowanceList = allowanceJsonList.map((item) => AllowanceDetail.fromJson(item)).toList(); // 解析营养成分列表 var nutrientJsonList = json['nutrient_guideline_detail'] as List; List<NutrientDetail> nutrientList = nutrientJsonList.map((item) => NutrientDetail.fromJson(item)).toList(); return PetData( dailyAllowance: allowanceList, nutrientGuidelines: nutrientList, ); } }
2. 编写API请求方法
假设你用http包来请求API,这里写一个示例请求逻辑:
import 'dart:convert'; import 'package:http/http.dart' as http; Future<PetData> fetchPetNutritionData() async { // 替换成你的真实API地址 final response = await http.get(Uri.parse('https://your-api-url.com/pet/17')); if (response.statusCode == 200) { // 解析JSON并转成PetData对象 Map<String, dynamic> rawData = jsonDecode(response.body); return PetData.fromJson(rawData); } else { throw Exception('加载宠物数据失败,请检查API连接'); } }
3. 计算比例并渲染柱状图
你的ChartLine组件需要rate(0-1之间的比例值),我们分别处理宏观/微观两类数据:
- 宏观膳食数据:直接用百分比除以100得到rate(比如60%就是0.6)
- 微观营养数据:先找到所有营养成分的最大值,再用每个成分的数值除以最大值得到相对比例(保证最长的柱子占满宽度)
然后用FutureBuilder来异步获取数据并渲染:
class PetNutritionChartPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Gummy的营养数据')), body: FutureBuilder<PetData>( future: fetchPetNutritionData(), builder: (context, snapshot) { if (snapshot.hasData) { PetData petData = snapshot.data!; // 生成宏观膳食柱状图列表 List<Widget> allowanceChartList = petData.dailyAllowance.map((detail) { double rate = detail.percentage / 100.0; return ChartLine( title: detail.name, number: detail.percentage, // 显示百分比数值 rate: rate, ); }).toList(); // 生成微观营养柱状图列表 double maxNutrientValue = petData.nutrientGuidelines .map((n) => n.amount) .reduce((a, b) => a > b ? a : b); // 找最大值 List<Widget> nutrientChartList = petData.nutrientGuidelines.map((detail) { double rate = detail.amount / maxNutrientValue; return ChartLine( title: detail.name, number: detail.amount.round(), // 取整显示营养数值 rate: rate, ); }).toList(); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('宏观膳食占比', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 16), ...allowanceChartList, const SizedBox(height: 32), const Text('微观营养成分', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 16), ...nutrientChartList, ], ), ); } else if (snapshot.hasError) { return Center(child: Text('加载出错:${snapshot.error}')); } // 加载中状态 return const Center(child: CircularProgressIndicator()); }, ), ); } }
小提示
- 如果你的
ChartLine需要显示小数数值,可以把number字段改成double类型,或者用detail.amount.toStringAsFixed(1)来保留一位小数 - 如果API返回的营养数据可能为空,记得加空值判断,避免报错
- 你可以根据需求调整
ChartLine的样式(比如柱子颜色、文字大小等)
内容的提问来源于stack exchange,提问作者Jason Waku
相关产品推荐
相关产品推荐

