Flutter学习:几个Demo学习Animation、Tween和CustomPaint的使用,

参考文档:https://medium.com/flutter/zero-to-one-with-flutter-part-two-5aa2f06655cb
原github地址:https://github.com/mravn/charts

概述

本篇文章从几个Demo出发介绍了Animation、Tween和CustomPaint的使用,并且几个Demo实现效果是层层递进的,下面会详细记录Demo的编写过程,以及简要地介绍以上三个类的使用和注意事项。本文涉及到的知识点:

  • Animation、Tween和CustomPaint的使用
  • 单个bar的动画
  • barChart的动画

单个bar动画Demo

首先上图,该demo完成后的效果如下:
Flutter学习:几个Demo学习Animation、Tween和CustomPaint的使用,_第1张图片
那么如何实现这么一个效果呢?这就需要使用到Animation、Tween和CustomPaint这三个类了,需要以下几个步骤:

  • 需要创建一个Bar类,用来表示bar的属性;
  • 创建一个继承Tween的BarTween类,并且必须重写lerp方法,待会从源码角度分析;
  • 创建一个继承CustomPainter的BarPainter类,在其内部基于画板canvas,用Paint类创建画笔(并且可以指定画笔的属性),来绘制图形。
  • 创建一个继承StatefulWidget的BarPage类,用状态来刷新界面显示

Bar类

class Bar {
  static final Random _random = new Random();
  static final double _maxHeight = 100.0;
  final Color color;
  final double height;

  Bar(this.color, this.height);

  /// 空bar
  factory Bar.empty() => Bar(Colors.transparent, 0.0);

  /// 带有随机颜色和随机高度的bar,高度范围
  factory Bar.random() =>
      Bar(ColorPalette.primary.random(), _random.nextDouble() * _maxHeight);

  static Bar lerp(Bar begin, Bar end, double t) {
    return Bar(Color.lerp(begin.color, end.color, t),
        lerpDouble(begin.height, end.height, t));
  }
}

值得注意的是,以上使用了factory工厂构造函数创建对象,还有在lerp静态函数中,使用了Color.lerplerpDouble(这个需要引入dart:math包),这个方法是为了之后的BarTween对象服务。

BarTween类

/// Bar属性线性变换插值器
class BarTween extends Tween {
  BarTween(Bar begin, Bar end) : super(begin: begin, end: end);

  @override
  Bar lerp(double t) {
    return Bar.lerp(begin, end, t);
  }
}

其中lerp在本demo中必须重写,否则会没有动画过程。这个方法指代的是在动画t位置的状态,t的取值为[0.0,1.0]。根据Tween源代码可以看到,transform方法中调用了lerp,而在lerp方法中只是对begin,end进行了+,-,*计算当前t的状态,并且本例Bar类并没有对这几个操作符进行重写,但是在我注释掉Bar类的重写之后,也没有报错,这个就搞不清了。

  /// Returns the value this variable has at the given animation clock value.
  ///
  /// The default implementation of this method uses the [+], [-], and [*]
  /// operators on `T`. The [begin] and [end] properties must therefore be
  /// non-null by the time this method is called.
  @protected
  T lerp(double t) {
    assert(begin != null);
    assert(end != null);
    return begin + (end - begin) * t;
  }

  /// Returns the interpolated value for the current value of the given animation.
  ///
  /// This method returns `begin` and `end` when the animation values are 0.0 or
  /// 1.0, respectively.
  ///
  /// This function is implemented by deferring to [lerp]. Subclasses that want
  /// to provide custom behavior should override [lerp], not [transform] (nor
  /// [evaluate]).
  ///
  /// See the constructor for details about whether the [begin] and [end]
  /// properties may be null when this is called. It varies from subclass to
  /// subclass.
  @override
  T transform(double t) {
    if (t == 0.0)
      return begin;
    if (t == 1.0)
      return end;
    return lerp(t);
  }

BarPainter

Bar类和动画变换规则类BarTween都已经编写就绪,下面就是绘制bar图形了。

/// BarChart绘制器 根据传入的动画对象,在画板canvas上用画笔paint绘制出对应的图形
class BarPainter extends CustomPainter {
  final Animation animation;
  static const barWidth = 10.0;

  /// 需要将动画对象传递进来,并且指定repaint:animation
  /// repaint是The painter will repaint whenever `repaint` notifies its listeners.
  BarPainter(Animation animation)
      : animation = animation,
        super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    final bar = animation.value;
    final paint = new Paint()
      ..color = bar.color
      ..style = PaintingStyle.fill;
    canvas.drawRect(
        Rect.fromLTWH((size.width - barWidth) / 2.0, size.height - bar.height,
            barWidth, bar.height),
        paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

关于画板Canvas和画笔Paint对象的使用,和Android原生使用类似,Canvas中提供了以下绘制方法:

  • void drawLine(Offset p1, Offset p2, Paint paint) 绘制直线
  • void drawRect(Rect rect, Paint paint) 绘制矩形
  • void drawRRect(RRect rrect, Paint paint) 绘制圆角矩形
  • void drawDRRect(RRect outer, RRect inner, Paint paint) 绘制内外两个圆角矩形
  • void drawOval(Rect rect, Paint paint) 绘制椭圆
  • void drawCircle(Offset c, double radius, Paint paint) 绘制圆形
  • void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) 绘制弧线
  • 。。。

SingleBarPage类

class SingleBarPage extends StatefulWidget {
  @override
  _SingleBarPageState createState() => _SingleBarPageState();
}

class _SingleBarPageState extends State
    with SingleTickerProviderStateMixin {
  AnimationController _animation;
  BarTween _barTween;

  @override
  void initState() {
    super.initState();
    _animation =
        AnimationController(vsync: this, duration: Duration(milliseconds: 300));
    _barTween = BarTween(Bar.empty(), Bar.random());
    _animation.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Charts"),
      ),
      body: Center(
        child: CustomPaint(
          painter: BarPainter(_barTween.animate(_animation)),
          size: Size(200.0, 100.0),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: changeData,
      ),
    );
  }

  // 通过改变BarTween,指定初始和结束Bar状态,来达到动画的效果
  void changeData() {
    setState(() {
      _barTween = BarTween(_barTween.evaluate(_animation), Bar.random());
      _animation.forward(from: 0.0);
    });
  }
}

值得主要的是,一个Bar的动画,需要指定AnimationController动画控制器、BarTween动画变换规则。还有bar图形的绘制需要用CustomPaint承载。

  • AnimationController需要指定TickerProvider,这个用来创建一个ticker来进行动画操作,那么TickerProvider如何创建呢?一般是指定当前类with TickerProvider或者其子类。
  • Tween类继承了Animatable类,其主要的一些方法有:
    • T transform(double t); 返回t位置的状态
    • T evaluate(Animation animation) :内部调用了transform,同样是返回动画的当前状态
    • Animation animate(Animation parent):返回一个新的Animation对象
    • Animatable chain(Animatable parent):链式动画

Barchart动画

同样先上图,效果如下:

与第一个动画实例类似,也是需要四个步骤:

  • 创建BarChart类,里面需要指定一个List来包含需要绘制的bars
  • 创建BarChartTween
  • 创建BarChartPainter,绘制每一个状态下的BarChart,以及变成这个状态的动画
  • 创建BarChartPage,封装成一个空间,可以在主页面上显示。
    代码如下:
import 'package:flutter/material.dart';

import 'bar.dart';

class BarChart {
  static const int barCount = 5;
  final List bars;

  BarChart(this.bars) : assert(bars.length == barCount);

  factory BarChart.empty() => BarChart(List.filled(barCount, Bar.empty()));

  factory BarChart.random() =>
      BarChart(List.generate(barCount, (i) => Bar.random()));

  static BarChart lerp(BarChart begin, BarChart end, double t) {
    return BarChart(List.generate(
        barCount, (i) => Bar.lerp(begin.bars[i], end.bars[i], t)));
  }
}

class BarChartTween extends Tween {
  BarChartTween(BarChart begin, BarChart end) : super(begin: begin, end: end);

  @override
  BarChart lerp(double t) {
    return BarChart.lerp(begin, end, t);
  }
}

class BarChartPainter extends CustomPainter {
  Animation animation;
  static const double barWidthFraction = 0.75;

  BarChartPainter(this.animation) : super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    void drawBar(Bar bar, double x, double barWidth, Paint paint) {
      // 将bar的颜色赋给画笔
      paint..color = bar.color;
      canvas.drawRect(
          Rect.fromLTWH(x, size.height - bar.height, barWidth, bar.height),
          paint);
    }

    final paint = Paint()..style = PaintingStyle.fill;
    final chart = animation.value;
    final barDistance = size.width / (1 + chart.bars.length);
    final barWidth = barDistance * barWidthFraction;

    // bar的x轴开始位置
    var x = barDistance - barWidth / 2;

    chart.bars.forEach((bar) {
      drawBar(bar, x, barWidth, paint);
      x += barDistance;
    });
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

这篇文章到此打住,已经够长了。下一篇文章介绍几种更加复杂的Bar Demo。

该demo项目,完整代码,我的github地址:
https://github.com/shengleiRain/flutter_app/tree/master/animation_bar_demo

你可能感兴趣的:(flutter)