Flutter fl_chart LineChart跨4天以上数据时渲染报错
Flutter fl_chart LineChart iOS GPU内存错误解决方案
问题
使用fl_chart的LineChart组件时,iOS物理设备上时间跨度超过4天会触发GPU内存错误:
Execution of the command buffer was aborted due to an error during execution. Too much geometry to support memoryless render pass attachments. (0000000d:kIOGPUCommandBufferCallbackErrorOutOfMemoryForParameterBuffer)
测试场景:minx设为6天前时间戳时触发,4天内正常,有时需热重载1-2次复现。最小测试代码如下:
import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; class Playground extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { List<Color> gradientColors = [ Theme.of(context).colorScheme.primary.withOpacity(.5), Theme.of(context).colorScheme.primary, ]; List<FlSpot> test_spots = [ // FlSpot( // Timestamp.fromDate(DateTime.now().subtract(const Duration(days: 4))).millisecondsSinceEpoch.toDouble() / 1000, // 167.0 // ) ]; double minx = Timestamp.fromDate(DateTime.now().subtract(const Duration(days: 6))).millisecondsSinceEpoch.toDouble() / 1000; double maxx = Timestamp.fromDate(DateTime.now()).millisecondsSinceEpoch.toDouble() / 1000; double miny = 150; double maxy = 200; return Padding( padding: const EdgeInsets.fromLTRB(0, 100, 0, 0), child: Stack( children: <Widget>[ AspectRatio( aspectRatio: 1.70, child: Padding( padding: const EdgeInsets.only( right: 18, left: 12, top: 24, bottom: 12, ), child: LineChart( LineChartData( gridData: FlGridData( show: true, drawVerticalLine: true, horizontalInterval: 1, verticalInterval: 1, getDrawingHorizontalLine: (value) { return const FlLine( color: Colors.white, strokeWidth: 1, ); }, getDrawingVerticalLine: (value) { return const FlLine( color: Colors.white, strokeWidth: 1, ); }, ), titlesData: const FlTitlesData( show: true, rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: bottomTitleWidgets, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: leftTitleWidgets, reservedSize: 42, ), ), ), borderData: FlBorderData( show: true, border: Border.all(color: Theme.of(context).colorScheme.tertiary), ), minX: minx, maxX: maxx, minY: miny, maxY: maxy, lineBarsData: [ LineChartBarData( spots: test_spots, isCurved: false, gradient: LinearGradient( colors: gradientColors, ), barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData( show: false, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: gradientColors .map((color) => color.withOpacity(0.3)) .toList(), ), ), ), ], ) ), ), ), ], ) ); } } Widget bottomTitleWidgets(double value, TitleMeta meta) { if(value == meta.min || value == meta.max){ return const Text(""); } Widget text = Text( DateFormat('Md').format(DateTime.fromMillisecondsSinceEpoch((value * 1000).round())), style: TextStyle( color: Colors.grey[600], fontSize: 10, ) ); return SideTitleWidget( axisSide: meta.axisSide, child: text, ); } Widget leftTitleWidgets(double value, TitleMeta meta) { return Text( value.toInt().toString(), style: TextStyle( color: Colors.grey[600], fontSize: 15, ), textAlign: TextAlign.center ); }
原因
你的猜测正确:verticalInterval: 1设置下,X轴单位是秒(时间戳除以1000),6天跨度对应518400个单位,组件会渲染50多万条垂直线,直接耗尽GPU内存。
解决方案
无需更换图表库,调整以下参数即可解决:
1. 调整网格线垂直间隔
将verticalInterval改为合理的时间单位(比如1天=86400秒),减少垂直线数量:
gridData: FlGridData( show: true, drawVerticalLine: true, horizontalInterval: 1, verticalInterval: 86400, // 按天间隔 // ...其他配置 ),
2. 转换为相对时间戳缩小数值范围
不用原始时间戳,改用相对于起始时间的差值作为X轴数值,大幅降低数值跨度:
// 定义起始时间 final startDate = DateTime.now().subtract(const Duration(days: 6)); // 计算相对秒数 double minx = 0; double maxx = Timestamp.fromDate(DateTime.now()).millisecondsSinceEpoch.toDouble() / 1000 - Timestamp.fromDate(startDate).millisecondsSinceEpoch.toDouble() / 1000;
同时更新bottomTitleWidgets,把相对时间转回原始日期:
// 在Playground组件中定义startDate,或者通过闭包传递 final startDate = DateTime.now().subtract(const Duration(days: 6)); Widget bottomTitleWidgets(double value, TitleMeta meta) { if(value == meta.min || value == meta.max){ return const Text(""); } // 相对秒数转原始日期 final date = startDate.add(Duration(seconds: value.round())); return SideTitleWidget( axisSide: meta.axisSide, child: Text( DateFormat('Md').format(date), style: TextStyle( color: Colors.grey[600], fontSize: 10, ) ), ); }
3. 可选:关闭垂直网格线
如果不需要垂直网格线,直接禁用:
gridData: FlGridData( drawVerticalLine: false, // ...其他配置 ),
验证
修改后,X轴网格线数量被控制在合理范围,不会再触发GPU内存错误。
内容的提问来源于stack exchange,提问作者liamhp
相关产品推荐
相关产品推荐

