You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter 3.3.9中定制fl_chart 0.61.0以匹配Figma设计

定制fl_chart 0.61.0匹配Figma设计方案

目标设计图

Figma目标设计图

需求:将fl_chart 0.61.0定制为与上述设计一致,使用Flutter版本3.3.9,以下是当前实现代码:

import 'package:flutter/material.dart';

import '../app_color.dart';
import 'chart/fl_chart.dart';

class AppLineChart extends StatefulWidget {
  final List<dynamic> dataList;
  final String? direction;
  final double? maxY;

  const AppLineChart({Key? key, required this.dataList, this.direction, this.maxY}) : super(key: key);

  @override
  State<AppLineChart> createState() => _AppLineChartState();
}

class _AppLineChartState extends State<AppLineChart> {
  List<Color> gradientColors = [
    const Color(0xff23b6e6),
    const Color(0xff02d39a),
  ];

  List<FlSpot> flSpots = [];
  bool isAvg = true;

  @override
  void initState() {
    super.initState();

    for (int i = 0; i < widget.dataList.length; i++) {
      flSpots.add(
        FlSpot(i.toDouble(), widget.dataList[i]),
      );
    }

    Future.delayed(const Duration(milliseconds: 500), () {
      setState(() {
        isAvg = false;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1.7,
      child: DecoratedBox(
        decoration: const BoxDecoration(
          borderRadius: BorderRadius.all(
            Radius.circular(18),
          ),
          color: AppColor.backgroundColor,
        ),
        child: Padding(
          padding: const EdgeInsets.only(
            right: 48,
            left: 48,
            top: 24,
            bottom: 24,
          ),
          child: LineChart(isAvg ? avgData() : mainData()),
          // child: LineChart(avgData()),
        ),
      ),
    );
  }

  Widget bottomTitleWidgets(double value, TitleMeta meta) {
    const style = TextStyle(
      color: AppColor.white,
      fontWeight: FontWeight.w500,
      fontFamily: 'SVN-Gilroy',
      fontSize: 16,
    );

    Widget text;
    switch (value.toInt()) {
      case 0:
        text = const Text('00', style: style);
        break;
      case 6:
        text = const Text('06', style: style);
        break;
      case 12:
        text = const Text('12', style: style);
        break;
      case 18:
        text = const Text('18', style: style);
        break;
      case 23:
        text = const Text('24', style: style);
        break;
      default:
        text = const Text('', style: style);
        break;
    }

    return SideTitleWidget(
      axisSide: meta.axisSide,
      child: text,
    );
  }

  LineChartData mainData() {
    return LineChartData(
      gridData: FlGridData(
        show: false,
        drawVerticalLine: true,
        horizontalInterval: 1,
        verticalInterval: 1,
        getDrawingHorizontalLine: (value) {
          return FlLine(
            color: const Color(0xff37434d),
            strokeWidth: 2,
          );
        },
        getDrawingVerticalLine: (value) {
          return FlLine(
            color: const Color(0xff37434d),
            strokeWidth: 2,
          );
        },
      ),
      titlesData: FlTitlesData(
        show: true,
        rightTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
        topTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
        bottomTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            reservedSize: 30,
            interval: 1,
            getTitlesWidget: bottomTitleWidgets,
          ),
        ),
        leftTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: false,
          ),
        ),
      ),
      borderData: FlBorderData(
        show: false,
        border: Border.all(color: const Color(0xff37434d)),
      ),
      minX: 0,
      maxX: 24,
      minY: 0,
      maxY: widget.maxY == null ? 42 : widget.maxY!,
      lineBarsData: [
        LineChartBarData(
          spots: flSpots,
          isCurved: true,
          gradient: LinearGradient(
            colors: gradientColors,
          ),
          barWidth: 1.5,
          isStrokeCapRound: false,
          dotData: FlDotData(
            show: true,
            getDotPainter: (spot, percent, barData, index) {
              if (index == DateTime.now().hour) {
                return FlDotCirclePainter(
                    radius: 4, color: gradientColors[0], strokeWidth: 2, strokeColor: gradientColors[0]);
              } else {
                return FlDotCirclePainter(
                  radius: 0,
                  color: gradientColors[0],
                  strokeWidth: 2,
                  strokeColor: Colors.transparent,
                );
              }
            },
          ),
          belowBarData: BarAreaData(
            show: false,
            gradient: LinearGradient(
              colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(),
            ),
          ),
        ),
      ],
    );
  }

  LineChartData avgData() {
    return LineChartData(
      lineTouchData: LineTouchData(enabled: false),
      gridData: FlGridData(
        show: false,
        drawHorizontalLine: true,
        verticalInterval: 1,
        horizontalInterval: 1,
        getDrawingVerticalLine: (value) {
          return FlLine(
            color: const Color(0xff37434d),
            strokeWidth: 2,
          );
        },
        getDrawingHorizontalLine: (value) {
          return FlLine(
            color: const Color(0xff37434d),
            strokeWidth: 2,
          );
        },
      ),
      titlesData: FlTitlesData(
        show: true,
        bottomTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            reservedSize: 30,
            getTitlesWidget: bottomTitleWidgets,
            interval: 1,
          ),
        ),
        leftTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: false,
          ),
        ),
        topTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
        rightTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
      ),
      borderData: FlBorderData(
        show: false,
      ),
      minX: 0,
      maxX: 23,
      minY: 0,
      maxY: widget.maxY == null ? 42 : widget.maxY!,
      lineBarsData: [
        LineChartBarData(
          spots: const [
            FlSpot(0, 10),
            FlSpot(1, 10),
            FlSpot(2, 10),
            FlSpot(3, 10),
            FlSpot(4, 10),
            FlSpot(5, 10),
            FlSpot(6, 10),
            FlSpot(7, 10),
            FlSpot(8, 10),
            FlSpot(9, 10),
            FlSpot(10, 10),
            FlSpot(11, 10),
            FlSpot(12, 10),
            FlSpot(13, 10),
            FlSpot(14, 10),
            FlSpot(15, 10),
            FlSpot(16, 10),
            FlSpot(17, 10),
            FlSpot(18, 10),
            FlSpot(19, 10),
            FlSpot(20, 10),
            FlSpot(21, 10),
            FlSpot(22, 10),
            FlSpot(23, 10),
          ],
          isCurved: true,
          gradient: LinearGradient(
            colors: [
              ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!,
              ColorTween(begin: gradientColors[0], end: gradientColors[1]).lerp(0.2)!,
            ],
          ),
          barWidth: 1.5,
          isStrokeCapRound: true,
          dotData: FlDotData(
            show: false,
          ),
          belowBarData: BarAreaData(
            show: false,
          ),
        ),
      ],
    );
  }
}

当前输出效果

当前输出效果

解决方案

对比目标设计与当前实现的差异,以下是针对性的代码修改:

1. 开启线条下方渐变填充

目标设计中线条到X轴之间有半透明渐变填充,修改mainData()里的belowBarData:

belowBarData: BarAreaData(
  show: true,
  gradient: LinearGradient(
    colors: gradientColors.map((color) => color.withOpacity(0.2)).toList(),
    begin: Alignment.topCenter,
    end: Alignment.bottomCenter,
  ),
  borderColor: Colors.transparent,
),

2. 调整线条视觉样式

  • 增加线条宽度匹配设计图
  • 设置线条两端为圆角
barWidth: 3, // 从1.5调整为3
isStrokeCapRound: true, // 从false改为true

3. 显示所有数据点并区分当前时间点

目标设计中所有数据点都显示为小圆点,当前时间点高亮显示,修改dotData:

dotData: FlDotData(
  show: true,
  getDotPainter: (spot, percent, barData, index) {
    if (index == DateTime.now().hour) {
      return FlDotCirclePainter(
        radius: 5,
        color: Colors.white,
        strokeWidth: 2,
        strokeColor: gradientColors[0],
      );
    } else {
      return FlDotCirclePainter(
        radius: 3,
        color: gradientColors[0],
        strokeWidth: 0,
      );
    }
  },
),

4. 优化底部刻度与竖线

目标设计中刻度上方有短竖线,修改bottomTitleWidgets并调整底部标题配置:

Widget bottomTitleWidgets(double value, TitleMeta meta) {
  const style = TextStyle(
    color: AppColor.white,
    fontWeight: FontWeight.w500,
    fontFamily: 'SVN-Gilroy',
    fontSize: 16,
  );

  Widget text;
  switch (value.toInt()) {
    case 0:
      text = const Text('00', style: style);
      break;
    case 6:
      text = const Text('06', style: style);
      break;
    case 12:
      text = const Text('12', style: style);
      break;
    case 18:
      text = const Text('18', style: style);
      break;
    case 23:
      text = const Text('24', style: style);
      break;
    default:
      return const SizedBox();
  }

  return Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      Container(
        width: 2,
        height: 8,
        color: AppColor.white.withOpacity(0.5),
      ),
      const SizedBox(height: 4),
      text,
    ],
  );
}

同时更新bottomTitles配置:

bottomTitles: AxisTitles(
  sideTitles: SideTitles(
    showTitles: true,
    reservedSize: 40, // 增加预留空间容纳竖线和文字
    interval: 6, // 直接设置间隔为6,减少不必要的刻度处理
    getTitlesWidget: bottomTitleWidgets,
  ),
),

5. 移除网格线

设计图无网格线,修改mainData()和avgData()里的gridData:

gridData: const FlGridData(
  show: false,
),

6. 调整容器内边距

适配新的刻度布局,修改Padding:

padding: const EdgeInsets.only(
  right: 24,
  left: 24,
  top: 16,
  bottom: 32, // 增加底部内边距
),

7. 修正X轴范围

将mainData()里的maxX从24改为23,匹配24个小时点的索引范围:

maxX: 23,

内容的提问来源于stack exchange,提问作者PNT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 09:27:03