Flutter 动态饼状图 让你的APP中无聊的统计图动起来 挻舒适的感觉瞬间提升一个档次 -深夜创作

优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力,当然也需要码农年轻灵活的思维,不局限于思维,不局限语言限制,才是编程的最高境界。

很多人会谈论到程序员35岁的问题,在我看来,无论是程序员还是各行各业中,真正限制人的不是年龄,是一颗持续学习的心,在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天、每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不妨来瞅瞅码农的轨迹。

本文章实现的效果如下图所示:

在这里插入图片描述

1 单文件测试

在 Flutter 项目中随便创建一个 dart 文件,然后就可以做为一个应用启动,只需要

//定义一个全局的内容主颜色
Color mainColor = Color(0xFFCADCED);

///程序入口
void main() {
  //启动根目录
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: TestPieAnimationPage(),
    ),
  );
}

然后默认的首页面就是一个 StatefulWidget,代码如下:

///默认显示的首页面
class TestPieAnimationPage extends StatefulWidget {
  @override
  _TestPieAnimationPageState createState() => _TestPieAnimationPageState();
}


class _TestPieAnimationPageState extends State<TestPieAnimationPage>
    with SingleTickerProviderStateMixin {
  ... ... 
}

首先初始化一些动画操作


  //来个动画控制器
  AnimationController _animationController;

  //控制背景抬高使用的
  Animation<double> _bgAnimation;

  //控制饼图使用的
  Animation<double> _progressAnimation;

  //控制数字使用的
  Animation<double> _numberAnimation;

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

    //初始化一下
    _animationController = new AnimationController(
        //执行时间为 1 秒
        duration: Duration(milliseconds: 1000),
        vsync: this);

    //在 0~500毫秒内 执行背景阴影抬高的操作
    _bgAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _animationController,
        //执行时间 区间
        curve: Interval(0.0, 0.5,curve: Curves.bounceOut),
      ),
    );

    //在 400 ~ 800 毫秒的区间内执行画饼的操作动画
    _progressAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _animationController,
        //执行时间 区间
        curve: Interval(0.4, 0.8,curve: Curves.bounceOut),
      ),
    );

    //在 700 ~ 1000 毫秒的区间 执行最上层的数字抬高的操作动画
    _numberAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _animationController,
        //执行时间 区间
        curve: Interval(0.7, 1.0,curve: Curves.bounceOut),
      ),
    );

    //添加 一个监听 刷新页面
    _animationController.addListener(() {
      setState(() {
      });
    });
  }

Curves.bounceOut 是动画曲线 如下图所示

在这里插入图片描述

2 build 方法页面主视图构建

在这里插入图片描述

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //页面的主内容 先来个居中
      body: Center(
        child: Container(
          //来个高度
          height: 260,
          //宽度填充
          width: MediaQuery.of(context).size.width,
          //设置一下背景
          color: mainColor,
          //封装一个方法构建左右排列的
          child: buildRow(),
        ),
      ),
      //右下角的悬浮按钮
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          //点击按钮开启动画
          _animationController.reset();
          _animationController.forward();
        },
      ),
    );
  }

2.1 通过Row左右排列 图例与饼图
  buildRow() {
    //左右排列的线性布局
    return Row(
      children: [
        //权重适配 占用宽度 5:6
        Expanded(
          flex: 5,
          //左边是一个竖直方向排列的线性布局
          child: buildLeftColumn(),
        ),
        //右边就是饼图区域
        Expanded(
          flex: 6,
          //层叠布局
          child: buildRightStack(),
        ),
      ],
    );
  }

在这里插入图片描述

2.2 左侧的图例

通过 Column 来竖直方向线性排列每一个图例条目,然后每一个图例条目再使用Row来水平排开小图标与文字,代码如下:


  //定义数据模型
  List _list = [
    {"title": "生活费", "number": 200, "color": Colors.lightBlueAccent},
    {"title": "交通费", "number": 200, "color": Colors.green},
    {"title": "贷款费", "number": 400, "color": Colors.amber},
    {"title": "游玩费", "number": 100, "color": Colors.orange},
    {"title": "电话费", "number": 100, "color": Colors.deepOrangeAccent},
  ];
  
  //左边
  Column buildLeftColumn() {
    return Column(
      //设置包裹子Widget
      mainAxisSize: MainAxisSize.min,
      //图例排列
      children: _list.map(
        (data) {
          return Container(
            //设置左右上下边距
            padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
            child: Row(
              children: [
                Container(
                  margin: EdgeInsets.only(right: 10),
                  decoration: BoxDecoration(
                    color: data["color"],
                    shape: BoxShape.circle,
                  ),
                  width: 10,
                  height: 10,
                ),
                Text(
                  data["title"],
                  style: TextStyle(fontSize: 16),
                ),
              ],
            ),
          );
        },
      ).toList(),
    );
  }

在这里插入图片描述

2.3 右侧的饼图

是通过 Stack 层叠布局将两个Container容器层叠在一起,阴影效果是通过 BoxDecoration 的 BoxShadow 来实现,代码如下:

  Stack buildRightStack() {
    return Stack(
      //子 Widget 居中
      alignment: Alignment.center,
      children: [
        //第一层
        Container(
          //来个内边距
          padding: EdgeInsets.all(22),
          //来个边框装饰
          decoration: BoxDecoration(color: mainColor, shape: BoxShape.circle,
              //来个阴影
              boxShadow: [
                BoxShadow(
                  color: Colors.white,
                  spreadRadius: -8 * _bgAnimation.value,
                  offset:
                      Offset(-5 * _bgAnimation.value, -5 * _bgAnimation.value),
                  blurRadius: 30 * _bgAnimation.value,
                ),
                BoxShadow(
                  //模糊颜色
                  color: Colors.blue[300].withOpacity(0.3),
                  //模糊半径
                  spreadRadius: 2 * _bgAnimation.value,
                  //阴影偏移量
                  offset:
                      Offset(5 * _bgAnimation.value, 5 * _bgAnimation.value),
                  //模糊度
                  blurRadius: 20 * _bgAnimation.value,
                ),
              ]),
          //开始绘制神操作
          child: CustomPaint(
            size: Size(200, 200),
            painter: CustomShapPainter(_list, _progressAnimation.value),
          ),
        ),
        //第二层
        Container(
          width: 100,
          decoration: BoxDecoration(
            color: mainColor,
            shape: BoxShape.circle,
            boxShadow: [
              BoxShadow(
                  spreadRadius: 3 * _numberAnimation.value,
                  blurRadius: 5 * _numberAnimation.value,
                  offset: Offset(
                      5 * _numberAnimation.value, 5 * _numberAnimation.value),
                  color: Colors.black54),
            ],
          ),
          child: Center(
            child: Text(
              "¥100",
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
            ),
          ),
        ),
      ],
    );
  }

在这里插入图片描述

2.4 绘制饼图

就是在自定义的 CustomPainter 中搞定的,代码如下:

class CustomShapPainter extends CustomPainter {
  //数据内容
  List list;

  double progress;

  CustomShapPainter(this.list, this.progress);

  //来个画笔
  Paint _paint = new Paint()..isAntiAlias = true;

  //圆周率(Pi)是圆的周长与直径的比值,一般用希腊字母π表示
  //绘制内容
  @override
  void paint(Canvas canvas, Size size) {
    //中心
    Offset center = Offset(size.width / 2, size.height / 2);
    //半径  取宽高 一半 最小值
    double radius = min(size.width / 2, size.height / 2);

    //开始绘制的弧度
    double startRadian = -pi / 2;

    //总金额
    double total = 0.0;
    list.forEach((element) {
      total += element["number"];
    });

    //开始绘制
    for (var i = 0; i < list.length; i++) {
      //当前要绘制的选项
      var item = list[i];

      //计算所占的比例
      double flag = item["number"] / total;

      //计算弧度
      double sweepRadin = flag * 2 * pi * progress;

      //开始绘制弧
      //设置一下画笔的颜色
      _paint.color = item["color"];
      canvas.drawArc(Rect.fromCircle(center: center, radius: radius),
          startRadian, sweepRadin, true, _paint);

      //累加下次开始绘制的角度
      startRadian += sweepRadin;
    }
  }

  //返回true 刷新
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

在这里插入图片描述

【x1】微信公众号的每日提醒 随时随记 每日积累 随心而过 文章底部扫码关注

【x2】各种系列的视频教程 免费开源 关注 你不会迷路

【x3】系列文章 百万 Demo 随时 复制粘贴 使用

【x4】简短的视频不一样的体验

【x5】必须有源码


不局限于思维,不局限语言限制,才是编程的最高境界。

以小编的性格,肯定是要录制一套视频的,随后会上传

有兴趣 你可以关注一下 西瓜视频 — 早起的年轻人

在这里插入图片描述

早起的年轻人 CSDN认证博客专家 移动开发 项目管理 Java
只要用心去做,每一件事情还是有可能成功的,当然成功是没有界限的,只不过是达到自己心里的那个目标,公众号:我的大前端生涯,一个爱喝茶的程序员,通常会搞搞SpringBoot 、Herbinate、Mybatiys、Android、iOS、Flutter、Vue、小程序等.
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页