flutter 图解_【Flutter 专题】83 图解自定义 ACEWave 波浪 Widget (一)

      和尚今天尝试一下绘制波浪的效果,虽然 pub 仓库中已经有成熟的插件,但和尚还是准备用之前学习的 CanvasAnimation 尝试自定义一个 ACEWave

1. 绘制曲线

      绘制波浪首先需要绘制曲线,采用 Canvas 绘制贝塞尔曲线;常用的是数学中通常用的 sin(x) / cos(y) 函数即可;

a7b476dc64cffdf16aecf220d4a23b18.png

      其中和尚通过 Canvas 绘制时使用了 path.quadraticBezierTo 来绘制从第一个 Point 到另一个 Point 的贝塞尔曲线;

class _ACEWavePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.red..strokeCap = StrokeCap.round
      ..strokeWidth = 10..style = PaintingStyle.stroke;
    Path path = Path()
      ..moveTo(0, 500)
      ..quadraticBezierTo(size.width / 4, 300, size.width / 2, 500)
      ..quadraticBezierTo(size.width / 4 * 3, 700, size.width, 500);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

a27a19992e1efacbd560c7de7fb5a52a.png

2. 循环动画

      和尚使用最常用的平移动画来让曲线动起来,其中注意的是:

  1. 当第一次动画结束时,通过 controller.repeat() 来实现循环播放;

  2. 动画需要使用 Curves.linear 线性动画,否则在循环播放过程中衔接不顺畅;

  3. 使用动画时均需在生命周期结束时 dispose() 销毁动画;

class _ACEWaveState extends State<ACEWave> with TickerProviderStateMixin {
  AnimationController _waveController;
  Animation<double> _waveAnimation;
  int _duration = 2000;
  CurvedAnimation _curvedAnimation;

  @override
  Widget build(BuildContext context) {
    return Transform.translate(
        offset: Offset(MediaQuery.of(context).size.width * _curvedAnimation.value, 0.0),
        child: Container(width: MediaQuery.of(context).size.width,
            child: CustomPaint(painter: _ACEWavePainter())));
  }

  _initAnimations() {
    _waveController = AnimationController(duration: Duration(milliseconds: _duration), vsync: this);
    _curvedAnimation = CurvedAnimation(parent: _waveController, curve: Curves.linear);
    _waveAnimation = Tween(begin: 0.0, end: 1.0).animate(_waveController);
    _waveAnimation.addListener(() => setState(() {}));
    _waveController.forward();
    _waveAnimation.addStatusListener((status) {
      switch (status) {
        case AnimationStatus.completed:
          _waveController.repeat();
          break;
        case AnimationStatus.dismissed:
          _waveController.forward();
          break;
        default:
          break;
      }
    });
  }

  _disposeAnimations() {
    _waveController.dispose();
  }

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

  @override
  void dispose() {
    _disposeAnimations();
    super.dispose();
  }
}

5dd179bdf14dca9c5c736cee9f8676e6.gif

3. 增加波浪周期

      在执行循环动画之后,发现动画过程中,会有一半是空白的,此时我们增加波浪的周期即可,多绘制一个屏幕的波浪即可,和尚建议前后多绘制两个屏幕的曲线,在循环过程中更流畅;

Path path = Path()
  ..moveTo(0 - size.width, 500)
  ..quadraticBezierTo(size.width / 4 - size.width, 300, size.width / 2 - size.width, 500)
  ..quadraticBezierTo(size.width / 4 * 3 - size.width, 700, size.width - size.width, 500)
  ..quadraticBezierTo(size.width / 4, 300, size.width / 2, 500)
  ..quadraticBezierTo(size.width / 4 * 3, 700, size.width, 500);

canvas.drawPath(path, paint);

50abb301411e0b2994673e3e7f09835f.gif

4. 调整波浪起始位置

      和尚尝试的曲线是 sin(x) 方式的,起始位置都是 (0.0, 0.0),然而多条波浪时不会都从起点开始;于是和尚提供了一个初始位置,来错开各波浪展示位置;

Path path = Path()
  ..moveTo(0 - size.width - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 - size.width - startOffset,
      500 - waveHeight, size.width / 2 - size.width - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 * 3 - size.width - startOffset,
      500 + waveHeight, size.width - size.width - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 - startOffset, 500 - waveHeight,
      size.width / 2 - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 * 3 - startOffset, 500 + waveHeight,
      size.width - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 + size.width - startOffset,
      500 - waveHeight, size.width / 2 + size.width - startOffset, 500)
  ..quadraticBezierTo(size.width / 4 * 3 + size.width - startOffset,
      500 + waveHeight, size.width + size.width - startOffset, 500);

ebb030588761e1b1f13cd45f8d8f207f.png

5. 调整波浪宽度和峰值

      和尚调整完波浪起始位置之后对于波浪的宽度和峰值也要进行调整,保证每条波浪效果略有不同;

      和尚预先绘制了前中后三个屏幕曲线,在测试过程中,若屏幕并非是曲线周期倍数时,衔接过程中会有空余,如图;

fe2c71c11d9eb1991124211b938ba1f6.png

      于是和尚计算波浪完整周期倍数与屏幕宽的差值作为移动点 moveTo 的附加宽度即可;

for (int i = 0; i   path..moveTo(waveWidth * i - size.width - startOffset, 500.0)
    ..quadraticBezierTo(
        _quaterWidth + waveWidth * i - size.width - startOffset,
        500 - waveHeight,
        _quaterWidth * 2 + waveWidth * i - size.width - startOffset,
        500.0)
    ..moveTo(
        _quaterWidth * 2 + waveWidth * i - size.width - startOffset, 500.0)
    ..quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i - size.width - startOffset,
        500 + waveHeight,
        _quaterWidth * 4 + waveWidth * i - size.width - startOffset,
        500.0)
    ..moveTo(waveWidth * i + startOffset + (plusWidth), 500.0)
    ..quadraticBezierTo(
        _quaterWidth + waveWidth * i + startOffset + plusWidth,
        500 - waveHeight,
        _quaterWidth * 2 + waveWidth * i + startOffset + plusWidth,
        500.0)
    ..moveTo(
        _quaterWidth * 2 + waveWidth * i + startOffset + plusWidth, 500.0)
    ..quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i + startOffset + plusWidth,
        500 + waveHeight,
        _quaterWidth * 4 + waveWidth * i + startOffset + plusWidth,
        500.0)
    ..moveTo(waveWidth * i - size.width + startOffset, 500.0)
    ..quadraticBezierTo(
        _quaterWidth + waveWidth * i - size.width + startOffset,
        500 - waveHeight,
        _quaterWidth * 2 + waveWidth * i - size.width + startOffset,
        500.0)
    ..moveTo(
        _quaterWidth * 2 + waveWidth * i - size.width + startOffset, 500.0)
    ..quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i - size.width + startOffset,
        500 + waveHeight,
        _quaterWidth * 4 + waveWidth * i - size.width + startOffset,
        500.0);
}

a68c9f9179990248c6761db1433315fa.gif


      至此,一个基本的波浪模型基本完成,但还有很多优化的方面,和尚在下篇中进一步绘制波浪效果;如有错误,请多多指导!

来源:阿策小和尚

a33402be1bf9e1105a5bd58d4bb4c52a.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/473001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++ 不插入重复元素但也不排序_面试时写不出排序算法?看这篇就够了

小Hub领读&#xff1a;本文主要详细讲述常见的八种排序算法的思想、实现以及复杂度。包括冒泡排序、快速排序、插入排序、希尔排序等等&#xff0c;文章讲解非常详细&#xff01;作者&#xff1a;静默虚空https://juejin.im/post/5cb6b8f551882532c334bcf2本文已归档到&#xf…

LintCode 1816. 使结果不超过阈值的最小除数(二分查找)

文章目录1. 题目2. 解题1. 题目 描述 给你一个整数数组 nums 和一个正整数 threshold &#xff0c;你需要选择一个正整数作为除数&#xff0c;然后将数组里每个数都除以它&#xff0c;并对除法结果求和。 请你找出能够使上述结果小于等于阈值 threshold 的除数中 最小 的那个…

马里兰大学calce电池循环测试数据集_千次循环,全程1.5V恒压,紫米新一代充电锂电池套装上手体验...

电池应该是每个家庭都会用到的东西&#xff0c;在现在智能家电飞迅发展的当下更是如此&#xff0c;比如智能门锁、智能门铃&#xff0c;都需要电池的供电&#xff0c;才能正常使用。而普通碱电池在电量耗尽后就无法循环使用&#xff0c;所以为了能节省成本减少电池对环境的污染…

TensorFlow 2.0 - TFRecord存储数据集、@tf.function图执行模式、tf.TensorArray、tf.config分配GPU

文章目录1. TFRecord 格式存储2. tf.function 高性能3. tf.TensorArray 支持计算图特性4. tf.config 分配GPU学习于&#xff1a;简单粗暴 TensorFlow 2 1. TFRecord 格式存储 使用该种格式&#xff0c;更高效地进行大规模的模型训练 import random import os import tensorfl…

c++ qt qlistwidget清空_Qt编写控件属性设计器12-用户属性

一、前言用户属性是后面新增加的一个功能&#xff0c;自定义控件如果采用的Q_PROPERTY修饰的属性&#xff0c;会自动识别到属性栏中&#xff0c;这个一般称为控件属性&#xff0c;在组态设计软件中&#xff0c;光有控件本身的控件属性还是不够的&#xff0c;毕竟这些属性仅仅是…

TensorFlow 2.0 - tf.saved_model.save 模型导出

文章目录1. tf.saved_model.save2. Keras API 模型导出学习于&#xff1a;简单粗暴 TensorFlow 2 1. tf.saved_model.save tf.train.Checkpoint 可以保存和恢复模型中参数的权值导出模型&#xff1a;包含参数的权值&#xff0c;计算图 无须源码即可再次运行模型&#xff0c;适…

机器人动力学与控制_力控制与位置控制的区别

1.背景介绍目前已经广泛落地的力控制方案是在机械臂末端安装多轴力矩传感器&#xff0c;用以检测机械臂对外界环境施加的力反馈值&#xff0c;并配合适当的控制策略&#xff0c;已达到控制机械臂与环境的作用力。这篇文章所要探讨的力控制&#xff08;上述力控制方案&#xff0…

基坑监测日报模板_基坑监测有多重要?实录基坑坍塌过程,不亲身经历,不知道现场有多恐怖!...

基坑整体坍塌不亲身经历&#xff0c;不知其恐怖▼前段时间&#xff0c;南宁绿地中央广场房地产项目D号地块(二期)基坑北侧约60米支护桩突然崩塌&#xff01;所幸无人伤亡。深基坑施工安全生产管理要点一、基坑开挖 1、 临边防护(1)基坑施工必须按要求进行&#xff0c;具体临边防…

[转]asp.net导出数据到Excel的三种方法

原文出处&#xff1a;asp.net导出数据到Excel的几种方法(1/3) 、asp.net导出数据到Excel的几种方法(2/3)、asp.net导出数据到Excel的几种方法(3/3) asp.net导出到Excel也是个老生常谈的问题&#xff0c;在此归纳一下。 第一种是比较常用的方法。是利用控件的RenderControl功能…

LintCode 378. 将二叉树转换成双链表(非递归遍历)

文章目录1. 题目2. 解题1. 题目 将一个二叉树按照中序遍历转换成双向链表。 样例 样例 1&#xff1a; 输入:4/ \2 5/ \1 3 输出: 1<->2<->3<->4<->5样例 2&#xff1a; 输入:3/ \4 1输出:4<->3<->1https://www.lintcode.com/pro…

js 将图片置灰_艾叶灰千万别扔——艾叶灰的神奇功效

请 点 上面“经络技巧”免费关注每晚9点准时免费更新点击下面图片阅读↓↓↓—— 以下是正文 ——艾灰的妙用1、宝宝经常会有红屁股&#xff0c;做妈妈的当然心疼&#xff0c;用了不少膏啊霜啊油啊&#xff0c;效果也是反反复复&#xff0c;尤其害怕会有依赖性。在妈妈的提醒下…

LintCode 434. 岛屿的个数II(并查集)

文章目录1. 题目2. 解题1. 题目 给定 n, m, 分别代表一个二维矩阵的行数和列数, 并给定一个大小为 k 的二元数组A. 初始二维矩阵全0. 二元数组A内的k个元素代表k次操作, 设第 i 个元素为 (A[i].x, A[i].y), 表示把二维矩阵中下标为A[i].x行A[i].y列的元素由海洋变为岛屿. 问在…

jqprintsetup已经安装还会提示_Windows 10更新将修复困扰用户已久的循环安装问题...

对于某些设备的用户来说&#xff0c;过去一年一直深受 Windows Update 陷入循环更新的问题困扰&#xff0c;尤其是那些使用英特尔驱动程序的设备。问题在于 Windows Update 会提示错误地提供不适配的驱动或版本&#xff0c;并且强行覆盖安装。此外即便用户已经安装了更新更好的…

springboot设置运行内存_Docker 如何运行多个 Springboot?

docker 如何运行多个Springboot &#xff1f;第一个&#xff1a;端口映射第二个&#xff1a;指定内存大小第三个&#xff1a;读取、写入物理文件第四个&#xff1a;日志文件第五个&#xff1a;多个容器内部网络访问第六个&#xff1a;遇到的问题第一个&#xff1a;端口映射Ngin…

LintCode 1915. 举重(01背包)

文章目录1. 题目2. 解题1. 题目 奥利第一次来到健身房&#xff0c;她正在计算她能举起的最大重量。 杠铃所能承受的最大重量为maxCapacity&#xff0c;健身房里有 n 个杠铃片&#xff0c;第 i 个杠铃片的重量为 weights[i]。 奥利现在需要选一些杠铃片加到杠铃上&#xff0c;使…

python实现简单线性回归和多元线性回归算法

1、问题引入 在统计学中&#xff0c;线性回归是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。一个带有一个自变量的线性回归方程代表一条直线。我们需要对线性回归结…

form表单通过checkbox_飞冰表单解决方案 - FormBinder

前言中后台业务场景中&#xff0c;表单是一种很常见的与用户交互的方式&#xff0c;从业务角度看&#xff0c;表单主要是收集用户的信息&#xff0c;而从技术角度看&#xff0c;作为一个通用型的组件&#xff0c;它要解决的问题无非就是三个&#xff1a;把一个初始数据对象扔给…

@data 重写set方法_C#中的类、方法和属性

这节讲C#中的类&#xff0c;方法&#xff0c;属性。这是面向对象编程中&#xff0c;我们最直接打交道的三个结构。类&#xff1a;类(class)是面向对象中最基本的单元&#xff0c;它是一种抽象&#xff0c;对现实世界中事物的抽象&#xff0c;在C#中使用class关键字声明一个类&a…

Docker安装+镜像拉取+容器+创建镜像+push to docker hub

文章目录1. 安装2. 镜像操作3. 容器4. docker hub本文参考&#xff1a;https://zhuanlan.zhihu.com/p/23599229 1. 安装 参考 https://www.runoob.com/docker/ubuntu-docker-install.html curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun测试&#xff…

css 百分比 怎么固定正方形_你未必知道的49个CSS知识点

本文的每一条&#xff0c;都是我曾经发过的掘金沸点&#xff0c;其中有很多条超过了百赞(窃喜)。鉴于时不时有童鞋翻我以前的沸点&#xff0c;因此&#xff0c;本文收集了个人目前发过的所有CSS知识点动图&#xff0c;以便阅读。需要说明的是&#xff0c;顺序仍是按当时发布顺序…