Flutter快速实现自定义折线图,支持数据改变过渡动画

简介: Flutter快速实现自定义折线图,支持数据改变过渡动画

最终效果如下:

绘制

先创建一个CustomPainter,使用canvas绘制。

import 'dart:ui';

import 'package:flutter/material.dart';

/// 自定义折线图
class LineChartPainter extends CustomPainter {
  Paint _outlinePaint;
  Paint _axisXPaint;
  Paint _valuePaint;

  /// 左边Y轴标签
  TextPainter _leftLabelPainter;
  List<int> _yTitle = [40, 30, 20, 10, 0, -10]; // y轴刻度数量
  List<double> _yData;
  final int averageY = 5; // y轴平均份数
  final int gapY = 10; // y轴平均间隔值
  final double _yTitlePadding = 5;

  LineChartPainter(this._yData, {Listenable repaint})
      : super(repaint: repaint) {
    _outlinePaint = Paint()
      ..color = Colors.grey
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;
    _axisXPaint = Paint()
      ..color = Colors.grey
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1;
    _valuePaint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.5;
    _leftLabelPainter = TextPainter()..textDirection = TextDirection.ltr;
  }

  @override
  void paint(Canvas canvas, Size size) {
    print("${this},,,$size");

    /// 外边框矩形
    canvas.drawRect(
        Rect.fromLTRB(0, 0, size.width, size.height), _outlinePaint);

    /// x轴
    for (var i = 0; i < 4; i++) {
      canvas.drawLine(Offset(0, size.height / averageY * (i + 1)),
          Offset(size.width, size.height / averageY * (i + 1)), _axisXPaint);
    }

    /// y轴标题
    for (var i = 0; i <= 5; i++) {
      _leftLabelPainter.text = TextSpan(
          text: _yTitle[i].toString(), style: TextStyle(color: Colors.black));
      _leftLabelPainter.layout();
      _leftLabelPainter.paint(
        canvas,
        Offset(-_leftLabelPainter.width - _yTitlePadding,
            size.height / 5 * i - _leftLabelPainter.height / 2),
      );
    }

    /// 数据
    /// 需要将y轴数据转换为height中对应比例的高度。
    var points = <Offset>[];
    for (var i = 0; i < _yData.length; i++) {
      points.add(Offset(size.width / _yData.length * i,
          translateValue(size.height, _yData[i])));
    }
    canvas.drawPoints(PointMode.polygon, points, _valuePaint);
  }

  @override
  bool shouldRepaint(LineChartPainter oldDelegate) {
    return oldDelegate._yData != _yData;
  }

  /// 转换y坐标
  double translateValue(double height, double rawValue) {
    /// y轴真实值总长度
    var valueSum = averageY * gapY;

    /// 真实值和height计算比例
    var scale = height / valueSum;
    var result = rawValue * scale;
    return height - result - height / averageY;
  }
}

实现过渡动画

使用CustomPaint包裹CustomPainter,为了在数据变化时,有动画效果,使用ImplicitlyAnimatedWidget。

实现动画效果的重点是forEachTweenlerp方法。

注意:State不要继承ImplicitlyAnimatedWidgetState,直接继承AnimatedWidgetBaseState即可,它内部实现了对AnimationController的监听,并实时刷新Animation的value,自动调用forEachTween方法。

import 'dart:ui';

import 'package:flutter/material.dart';

import 'line_chart_painter.dart';

/// 包装折线图 数据更新时展示过渡动画
class LineChart extends ImplicitlyAnimatedWidget {
  final Size size;
  final LineChartData lineChartData;
  final Duration duration;

  const LineChart(
      {Key key,
      this.size,
      @required this.lineChartData,
      @required this.duration})
      : super(key: key, duration: duration);

  @override
  _CustomCanvasState createState() {
    return _CustomCanvasState();
  }
}

class _CustomCanvasState extends AnimatedWidgetBaseState<LineChart> {
  DataTween _dataTween;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraint) {
        print(
            "widget size:${widget.size}  ${constraint.maxWidth}...${constraint.maxHeight}");
        var constrainSize =
            widget.size ?? Size(constraint.maxWidth, constraint.maxHeight);
        if (constrainSize.width == double.infinity) {
          throw FlutterError("必须为组件或父widget设置一个有效宽度");
        }
        if (constrainSize.height == double.infinity) {
          throw FlutterError("必须为组件或父widget设置一个有效高度");
        }
        return CustomPaint(
          foregroundPainter: LineChartPainter(
              _dataTween.evaluate(animation).data,
              repaint: animation),
          size: constrainSize,
        );
      },
    );
  }

  @override
  void forEachTween(TweenVisitor visitor) {
    /// 第一个参数是初始的tween,第二个参数是目标值,第三个是生成tween的回调。
    _dataTween = visitor(
        _dataTween, widget.lineChartData, (value) => DataTween(begin: value));
  }
}

class DataTween extends Tween<LineChartData> {
  DataTween({LineChartData begin, LineChartData end})
      : super(begin: begin, end: end);

  @override
  LineChartData lerp(double t) => LineChartData.lerp(begin, end, t);
}

class LineChartData {
  List<double> data;

  LineChartData({this.data});

  /// 计算动画更新时的数据
  /// begin表示动画开始时的数据,end是结束时的数据,t是动画估值器,从0到1,代表动画运行的进度。
  static LineChartData lerp(LineChartData begin, LineChartData end, double t) {
    /// 根据begin和end每个对应的值,因为值类型是double,所以使用系统自带的lerpDouble来计算值。
    LineChartData result;
    if (begin.data != null &&
        end.data != null &&
        begin.data.length == end.data.length) {
      result = LineChartData(
          data: List.generate(begin.data.length, (index) {
        return lerpDouble(begin.data[index], end.data[index], t);
      }));
    } else if (begin.data.length > end.data.length) {
      result = LineChartData(
          data: List.generate(end.data.length, (index) {
        return lerpDouble(begin.data[index], end.data[index], t);
      }));
    } else if (begin.data.length < end.data.length) {
      result = LineChartData(
          data: List.generate(begin.data.length, (index) {
        return lerpDouble(begin.data[index], end.data[index], t);
      }));
    }
    return result;
  }
}

使用

class StudyCanvasPage extends StatefulWidget {
  const StudyCanvasPage({Key key}) : super(key: key);

  @override
  _StudyCanvasPageState createState() => _StudyCanvasPageState();
}

class _StudyCanvasPageState extends State<StudyCanvasPage> {
  var check = false;
  List<double> _yData = [29.2, 29.8, 29.3, 29.2, 29.4, 29.1, 29.3, 29.2, 29.5, 29.2, 29.2, 29, 29.3, 29.2, 29.4, 29.1, 29.3, 29.2, 29.5, 29.2];
  List<double> _yData1 = [12.2, 19.8, 19.3, 9.2, 19.4, 19.1, 19.3, 19.2, 19.5, 19.2, 19.2, 19, 19.3, 19.2, 19.4, 19.1, 19.3, 19.2, 19.5, 19.2,];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('study canvas'),
      ),
      body: SafeArea(
        child: Container(
          child: Column(
            children: [
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    check = !check;
                  });
                },
                child: Text("切换数据"),
              ),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(28.0),
                  child: LineChart(
                    lineChartData:
                        LineChartData(data: check ? _yData : _yData1),
                    duration: Duration(milliseconds: 300),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


相关文章
|
3月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
353 4
|
27天前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
116 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
3月前
|
缓存 监控 前端开发
优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面
本文探讨了优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面,并通过案例分析展示了具体措施和效果,强调了持续优化的重要性及未来优化方向。
118 10
|
16天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
121 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
24天前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
75 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
3月前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
110 8
|
3月前
|
开发工具 UED 容器
Flutter&鸿蒙next 实现长按录音按钮及动画特效
本文介绍了如何在 Flutter 中实现一个带有动画效果的长按录音按钮。通过使用 `GestureDetector` 监听长按手势,结合 `AnimatedContainer` 和 `AnimationController` 实现按钮的动画效果,以及 `flutter_sound` 插件完成录音功能。文章详细讲解了功能需求、实现思路和代码实现,帮助读者逐步掌握这一实用功能的开发方法。
165 5
|
3月前
|
UED 开发者 容器
Flutter&鸿蒙next 的 Sliver 实现自定义滚动效果
Flutter 提供了强大的滚动组件,如 ListView 和 GridView,但当需要更复杂的滚动效果时,Sliver 组件是一个强大的工具。本文介绍了如何使用 Sliver 实现自定义滚动效果,包括 SliverAppBar、SliverList 等常用组件的使用方法,以及通过 CustomScrollView 组合多个 Sliver 组件实现复杂布局的示例。通过具体代码示例,展示了如何实现带有可伸缩 AppBar 和可滚动列表的页面。
173 1
|
3月前
Flutter 自定义组件继承与调用的高级使用方式
本文深入探讨了 Flutter 中自定义组件的高级使用方式,包括创建基本自定义组件、继承现有组件、使用 Mixins 和组合模式等。通过这些方法,您可以构建灵活、可重用且易于维护的 UI 组件,从而提升开发效率和代码质量。
160 1
|
3月前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
157 1

热门文章

最新文章

  • 1
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 6
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 7
    零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
  • 8
    flutter3-dart3-dymall原创仿抖音(直播+短视频+聊天)商城app系统模板
  • 9
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 10
    【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 1
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    14
  • 2
    零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
    58
  • 3
    flutter3-dart3-dymall原创仿抖音(直播+短视频+聊天)商城app系统模板
    38
  • 4
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    141
  • 5
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    37
  • 6
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    73
  • 7
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    121
  • 8
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    75
  • 9
    flutter开发中Use ‘const’ with the constructor to improve performance. Try adding the ‘const’ keyword to the constructor invocation.报错如何解决-优雅草卓伊凡
    18
  • 10
    【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    27