Flutter 中如何繪製動畫

升級之路發表於2018-12-13

前言

本文分享下自定義完成動畫的實現方法,最終的效果圖如下

Flutter 中如何繪製動畫

原始碼地址

首先是繪圖

在Flutter中繪圖非常簡單。關鍵詞CustomPainter, CustomPaint, Canvas

在iOS/Android中我們繼承UIView/View重寫draw/onDraw方法在裡面執行畫圖操作。 在Flutter中稍微有點不一樣,我們使用CustomPaint(這是一個widget),它需要一個引數painter,這個引數的型別是一個抽象類CustomPainter。 我們需要實現這個類的兩個關鍵方法: paint,shouldRepaint。畫什麼就由paint決定,而只有shouldRepaint返回true的時候才會重繪。

實現void paint(Canvas canvas, Size size)這個方法,在iOS中我們使用UIBezierPathCore Graphics繪圖,在Flutter具體的繪製方法用這個canvas, 具體的API可以檢視官方文件

下面這個例子就是畫一段圓弧

class DemoPainter extends CustomPainter {
  final double _arcStart;
  final double _arcSweep;

  DemoPainter(this._arcStart, this._arcSweep);

  @override
  void paint(Canvas canvas, Size size) {
    double side = math.min(size.width, size.height);
    Paint paint = Paint()
      ..color = Colors.blue
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 4.0
      ..style = PaintingStyle.stroke;
    canvas.drawArc(
        Offset.zero & Size(side, side), _arcStart, _arcSweep, false, paint);
  }

  @override
  bool shouldRepaint(DemoPainter other) {
    return _arcStart != other._arcStart || _arcSweep != other._arcSweep;
  }
}
複製程式碼

使用的時候把DemoPainter的例項當做引數傳給CustomPaint就可以使用了,比如

Container(
  child: CustomPaint(painter: DemoPainter(0.0, math.pi)),
  height: 200.0,
  width: 200.0,
  color: Colors.deepOrange,
  padding: EdgeInsets.all(30.0),
)
複製程式碼

顯示效果

image

然後加動畫

Flutter的動畫也不復雜,關鍵詞AnimationController

Flutter中的動畫是基於Animation,這個物件本身是一個抽象類,在一段時間內依次產生一些值。我們使用封裝好的AnimationController來做動畫,它在螢幕重新整理的每一幀,產生一個新的值,預設情況是在給定的時間段內線性的生成0.0到1.0的數字。

AnimationController有個引數vsync 可以繫結到一個widget(需要widget擴充套件SingleTickerProviderStateMixin),當widget不顯示時,動畫定時器將會暫停,當widget再次顯示時,動畫定時器重新恢復執行。duration屬性可以設定持續時間。還有一些方法可以控制動畫forward啟動,reverse反轉,repeat重複。

AnimationControlleraddListeneraddStatusListener方法可以新增監聽,一個是值監聽一個是狀態監聽。值監聽常用在呼叫setState來觸發UI重建來實現動畫,狀態監聽用在動畫狀態變化的時候執行一些方法,比如在動畫結束時反轉動畫。

至此我們已經可以繪製動畫了,程式碼如下

class DemoWidget extends StatefulWidget {
  @override
  _DemoWidgetState createState() => _DemoWidgetState();
}

class _DemoWidgetState extends State<DemoWidget>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 1500))
          ..repeat()
          ..addListener(() {
            setState(() {});
          });
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: DemoPainter(0.0, _controller.value * math.pi * 2),
    );
  }
}

複製程式碼

image

可以藉助AnimatedBuilder改寫上文的initStatebuild方法,使檢視層級更加清楚,有助於封裝

  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 1500))
          ..repeat();
    super.initState();
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(animation: _controller, builder: (context, child) {
      return CustomPaint(
        painter: DemoPainter(0.0, _controller.value * math.pi * 2),
      );
    });
  }
複製程式碼

Tween與Curve

TweenCurve可以幫我們更好地控制Animation的值 一般的Animation會在給定的時間內線性的產生0.0到1.0的值

Tween可以把這些轉變成我們想要的型別或者是範圍 比如Tween(begin: math.pi * 1.5, end: math.pi * 1.5 + math.pi * 2).evaluate(_controller),就可以把值的範圍轉成1.5pi到3.5pi。

Curve是一個抽象類表示生成值的曲線, Curves已經定義了許多常用的曲線。

這裡Tween,Curve可以使用chain,evaluate,transform和Animation串起來使用

我們可以使用這些更改我們上文的例子,程式碼如下

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return CustomPaint(
            painter: DemoPainter(
              Tween(begin: math.pi * 1.5, end: math.pi * 3.5)
                  .chain(CurveTween(curve: Interval(0.5, 1.0)))
                  .evaluate(_controller),
              math.sin(Tween(begin: 0.0, end: math.pi).evaluate(_controller)) *
                  math.pi,
            ),
          );
        });
  }
複製程式碼

當然這裡第二個引數有更簡潔的寫法

  math.sin(_controller.value*math.pi) *math.pi
複製程式碼

顯示效果

image

本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@白爾摩斯

相關文章