flutter 繪制流水(水波上升)動態(tài)效果

效果

你可以先簡單理解下貝塞爾曲線的原理:

推薦這個關(guān)于貝塞爾的教程:http://www.html-js.com/article/1628

代碼:

1.創(chuàng)建繪制波浪邊界的代碼

創(chuàng)建一個基礎(chǔ)的繪制類硝全,可接收動畫的x和y值:

import 'package:flutter/material.dart';

abstract class BasePainter extends CustomPainter{
  Animation<double> _xAnimation;
  Animation<double> _yAnimation;

  set XAnimation(Animation<double> value) {
    _xAnimation = value;
  }

  set YAnimation(Animation<double> value) {
    _yAnimation = value;
  }

  Animation<double> get YAnimation => _yAnimation;

  Animation<double> get XAnimation => _xAnimation;

}

實現(xiàn)

歡迎去瀏覽原文:http://tryenough.com/flutter-wave

import 'dart:math';

import 'package:flutter_wave/painter_base.dart';
import 'package:flutter/material.dart';

class WavePainter extends BasePainter {
  int waveCount;
  int crestCount;
  double waveHeight;
  List<Color> waveColors;
  double circleWidth;
  Color circleColor;
  Color circleBackgroundColor;
  bool showProgressText;
  TextStyle textStyle;

  WavePainter(
      {this.waveCount = 1,
        this.crestCount = 2,
        this.waveHeight,
        this.waveColors,
        this.circleColor = Colors.grey,
        this.circleBackgroundColor = Colors.white,
        this.circleWidth = 5.0,
        this.showProgressText = true,
        this.textStyle = const TextStyle(
          fontSize: 60.0,
          color: Colors.blue,
          fontWeight: FontWeight.bold,
          shadows: [
            Shadow(color: Colors.grey, offset: Offset(5.0, 5.0), blurRadius: 5.0)
          ],
        )});

  @override
  void paint(Canvas canvas, Size size) {
    double width = size.width;
    double height = size.height;

    if (waveHeight == null) {
      waveHeight = height / 10;
      height = height + waveHeight;
    }

    if (waveColors == null) {
      waveColors = [
        Color.fromARGB(
            100, Colors.blue.red, Colors.blue.green, Colors.blue.blue)
      ];
    }

    Offset center = new Offset(width / 2, height / 2);
    double xMove = width * XAnimation.value;
    double yAnimValue = 0.0;
    if (YAnimation != null) {
      yAnimValue = YAnimation.value;
    }
    double yMove = height * (1.0 - yAnimValue);
    Offset waveCenter = new Offset(xMove, yMove);

    var paintCircle = new Paint()
      ..color = Colors.grey
      ..style = PaintingStyle.fill
      ..strokeWidth = circleWidth
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 5.0);

//    canvas.drawCircle(center, min(width, height) / 2, paintCircle);

    List<Path> wavePaths = [];

    for (int index = 0; index < waveCount; index++) {
      double direction = pow(-1.0, index);
      Path path = new Path()
        ..moveTo(waveCenter.dx - width, waveCenter.dy)
        ..lineTo(waveCenter.dx - width, center.dy + height / 2)
        ..lineTo(waveCenter.dx + width, center.dy + height / 2)
        ..lineTo(waveCenter.dx + width, waveCenter.dy);

      for (int i = 0; i < 2; i++) {
        for (int j = 0; j < crestCount; j++) {
          double a = pow(-1.0, j);
          path
              ..quadraticBezierTo(
                waveCenter.dx +
                    width * (1 - i - (1 + 2 * j) / (2 * crestCount)),
                waveCenter.dy + waveHeight * a * direction,
                waveCenter.dx +
                    width * (1 - i - (2 + 2 * j) / (2 * crestCount)),
                waveCenter.dy);
        }
      }

      path..close();

      wavePaths.add(path);
    }
    var paint = new Paint()
      ..color = circleBackgroundColor
      ..style = PaintingStyle.fill
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 5.0);

    canvas.saveLayer(
        Rect.fromCircle(center: center, radius: min(width, height) / 2), paint);

//    canvas.drawCircle(center, min(width, height) / 2, paint);

    paint
//      ..blendMode = BlendMode.srcATop
      ..style = PaintingStyle.fill
      ..strokeWidth = 2.0
      ..maskFilter = MaskFilter.blur(BlurStyle.inner, 10.0);

    for (int i = 0; i < wavePaths.length; i++) {
      if (waveColors.length >= wavePaths.length) {
        paint.color = waveColors[i];
      } else {
        paint.color = waveColors[0];
      }
      canvas.drawPath(wavePaths[i], paint);
    }
//    paint.blendMode = BlendMode.srcATop;
    if (showProgressText) {
      TextPainter tp = TextPainter(
          text: TextSpan(
              text: '${(yAnimValue * 100.0).toStringAsFixed(0)}%',
              style: textStyle),
          textDirection: TextDirection.rtl)
        ..layout();

      tp.paint(
          canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
    }
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

2.創(chuàng)建工廠方法颖榜,用于創(chuàng)建波浪圖形

import 'package:flutter/material.dart';

import 'package:flutter_wave/painter_base.dart';
import 'package:flutter_wave/painter/painter_wave.dart';

abstract class BasePainterFactory {
  BasePainter getPainter();
}

class WavePainterFactory extends BasePainterFactory {
  BasePainter getPainter() {
    return WavePainter(
      waveCount: 1,
      waveColors: [
        Colors.lightBlueAccent[200],
      ],
      textStyle:
      TextStyle(
        fontSize: 60.0,
        foreground: Paint()
          ..color = Colors.lightBlue
          ..style = PaintingStyle.fill
          ..strokeWidth = 2.0
          ..blendMode = BlendMode.difference
          ..colorFilter = ColorFilter.mode(Colors.white, BlendMode.exclusion)
          ..maskFilter = MaskFilter.blur(BlurStyle.solid, 1.0),
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

給波浪添加動畫

推薦你先學(xué)一下動畫基礎(chǔ)知識:
https://juejin.im/post/5b6270edf265da0f473539a6

原理解釋:

xAnimation和yAnimation不斷的從0到1變化康栈,然后上面繪制波浪的地方根據(jù)這些值不斷的進行繪制,形成動畫算灸。

import 'package:flutter_wave/painter_factory.dart';
import 'package:flutter/material.dart';

class ProgressManager extends StatefulWidget {
  @override
  _ProgressManagerState createState() =>
      new _ProgressManagerState().._factory = WavePainterFactory();
}

class _ProgressManagerState extends State<ProgressManager>
    with TickerProviderStateMixin {
  AnimationController xController;
  AnimationController yController;
  Animation<double> xAnimation;
  Animation<double> yAnimation;
  List<double> _progressList = [];
  double curProgress = 0;
  BasePainterFactory _factory;

  set painter(BasePainterFactory factory) {
    _factory = factory;
  }

  setProgress(double progress) {
    _progressList.add(progress);
    onProgressChange();
  }

  onProgressChange() {
    if (_progressList.length > 0) {
      if (yController != null && yController.isAnimating) {
        return;
      }
      double nextProgress = _progressList[0];
      _progressList.removeAt(0);
      final double begin = curProgress;
      yController = new AnimationController(
          vsync: this, duration: Duration(milliseconds: 1000));
      yAnimation =
          new Tween(begin: begin, end: nextProgress).animate(yController);
      yAnimation.addListener(_onProgressChange);
      yAnimation.addStatusListener(_onProgressStatusChange);
      yController.forward();
    }
  }

  @override
  void initState() {
    super.initState();
    xController = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 4000));
    xAnimation = new Tween(begin: 0.0, end: 1.0).animate(xController);
    xAnimation.addListener(_change);
    yController = new AnimationController(
        vsync: this, duration: Duration(milliseconds: 5000));
    yAnimation = new Tween(begin: 0.0, end: 1.0).animate(yController);
    yAnimation.addListener(_onProgressChange);
    yAnimation.addStatusListener(_onProgressStatusChange);

    doDelay(xController, 0);

    Future.delayed(Duration(milliseconds: 3000), () {
      setProgress(0.66);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child:
      Container(
        width: MediaQuery.of(context).size.width,
        height: 400.0,
        child: new CustomPaint(
          painter: _factory.getPainter()
            ..XAnimation = xAnimation
            ..YAnimation = yAnimation,
          size: new Size(MediaQuery.of(context).size.width, 400.0),
        ),
      ),
    );
  }

  void _change() {
    setState(() {});
  }

  void _onProgressChange() {
    setState(() {
      curProgress = yAnimation.value;
    });
  }

  void _onProgressStatusChange(status) {
    if (status == AnimationStatus.completed) {
      onProgressChange();
    }
  }

  void doDelay(AnimationController controller, int delay) async {
    Future.delayed(Duration(milliseconds: delay), () {
      controller..repeat();
    });
  }

  @override
  void dispose() {
    xController.dispose();
    yController.dispose();
    xAnimation.removeListener(_change);
    yAnimation.removeListener(_onProgressChange);
    yAnimation.removeStatusListener(_onProgressStatusChange);
    super.dispose();
  }
}

使用的地方

body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ProgressManager(),
          ],
        ),
      ),

下載demo地址

http://tryenough.com/flutter-wave

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驻啤,隨后出現(xiàn)的幾起案子菲驴,更是在濱河造成了極大的恐慌,老刑警劉巖骑冗,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赊瞬,死亡現(xiàn)場離奇詭異,居然都是意外死亡贼涩,警方通過查閱死者的電腦和手機森逮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磁携,“玉大人褒侧,你說我怎么就攤上這事。” “怎么了闷供?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵烟央,是天一觀的道長。 經(jīng)常有香客問我歪脏,道長疑俭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任婿失,我火速辦了婚禮钞艇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豪硅。我一直安慰自己哩照,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布懒浮。 她就那樣靜靜地躺著飘弧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砚著。 梳的紋絲不亂的頭發(fā)上次伶,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音稽穆,去河邊找鬼冠王。 笑死,一個胖子當(dāng)著我的面吹牛舌镶,可吹牛的內(nèi)容都是我干的柱彻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乎折,長吁一口氣:“原來是場噩夢啊……” “哼绒疗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骂澄,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吓蘑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坟冲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磨镶,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年健提,在試婚紗的時候發(fā)現(xiàn)自己被綠了琳猫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡私痹,死狀恐怖脐嫂,靈堂內(nèi)的尸體忽然破棺而出统刮,到底是詐尸還是另有隱情,我是刑警寧澤账千,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布侥蒙,位于F島的核電站,受9級特大地震影響匀奏,放射性物質(zhì)發(fā)生泄漏鞭衩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一娃善、第九天 我趴在偏房一處隱蔽的房頂上張望论衍。 院中可真熱鬧,春花似錦聚磺、人聲如沸坯台。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捂人。三九已至御雕,卻和暖如春矢沿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酸纲。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工捣鲸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闽坡。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓栽惶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疾嗅。 傳聞我的和親對象是個殘疾皇子外厂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 很多熊友,抓住了千熊1.0的尾巴代承,成為了千熊汁蝶。當(dāng)然我也是。 進入了千熊的群论悴,有時候會看到群上有千熊抱怨小能熊的服務(wù)...
    日更版的Echo閱讀 427評論 0 2
  • 去年這個時候掖棉,陪你來回奔于醫(yī)院,坐在車上看路兩邊的繁花似錦膀估,你的生命在調(diào)落幔亥,春光也是黯然。 今年這個時候察纯,同樣是繁...
    人間二十四閱讀 224評論 1 0
  • 原文鏈接Storm Tutorial 本人原創(chuàng)翻譯帕棉,轉(zhuǎn)載請注明出處 這個教程內(nèi)容包含如何創(chuàng)建topologies及...
    quiterr閱讀 1,626評論 0 6
  • 板橋香伴,揚州八怪之一祖驱,詩書畫三棲全能藝人,他有一枚印章瞒窒,上刻康熙秀才雍正舉人乾隆進士捺僻。 論畫竹,無人能出其右崇裁;論任性...
    沐陽說閱讀 641評論 1 11