繪圖與動畫


動畫的應用之一,就是結合繪圖,例如,來做個重複畫圓表示下載中之類的動畫,為此,必須先有個可以畫圓餅的元件,這可以如下簡單地實作:

import 'package:flutter/material.dart';
import 'dart:math';

void main() => runApp(
  MaterialApp(
    home: Pie(radius: 100, sweepAngle: pi * 1.5),
  )
);

class Pie extends StatelessWidget {
  final startAngle = pi * 1.5;
  final double radius;
  final double sweepAngle;

  Pie({this.radius, this.sweepAngle});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.infinite,
      painter: PiePainter(radius: radius, sweepAngle: sweepAngle),
    );
  }
}

class PiePainter extends CustomPainter {
  final startAngle = pi * 1.5;
  final double radius;
  final double sweepAngle;

  PiePainter({this.radius, this.sweepAngle});

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = radius / 3
      ..style = PaintingStyle.stroke;

    canvas.drawArc(
        Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: radius),
        startAngle, sweepAngle,
        false,
        paint
    );
  }

  @override
  bool shouldRepaint(PiePainter other) => other.radius != radius || other.sweepAngle != sweepAngle;
}

執行起來的畫面如下:

繪圖與動畫

接著,因為 AnimatedWidget 的既有子類沒有適合的選項,這邊就直接使用 AnimatedBuilder

import 'package:flutter/material.dart';
import 'dart:math';

void main() => runApp(
  MaterialApp(
    home: Center(
      child: AnimatedPie(radius: 100, duration: Duration(seconds: 2)),
    ),
  )
);

class AnimatedPie extends StatefulWidget {
  final double radius;
  final Duration duration;

  AnimatedPie({this.radius, this.duration});

  @override
  State<StatefulWidget> createState() => _AnimatedPie();

}

class _AnimatedPie extends State<AnimatedPie> with SingleTickerProviderStateMixin {
  AnimationController _animation;

  @override
  void initState() {
    super.initState();
    _animation = AnimationController(
      // 0 到 2 * pi
      lowerBound: 0,
      upperBound: pi * 2, 
      duration: widget.duration,
      vsync: this,
    )..repeat(); // 不斷重複
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _animation,
        builder: (_, __) {
          // 將 _animation.value 的值傳入,作為圓餅的徑度
          return Pie(radius: widget.radius, sweepAngle: _animation.value);
        }
    );
  }
}

class Pie extends StatelessWidget {
  ...略
}

class PiePainter extends CustomPainter {
  ...略
}

完成的效果如下:

繪圖與動畫