Flutter動畫入門篇

QiShare發表於2021-07-01

什麼是動畫

動畫(Animation)是一系列靜止的影像以一定頻率連續變化而導致肉眼的視覺暫留。

為什麼需要動畫

無論是移動端還是Web端開發,當有和使用者互動的場景時,引入動畫,可以使應用體驗更流暢、使用者體驗更好,增強產品的可用性與趣味性。在 Flutter 中建立動畫可以有多種不同實現方式,可以輕鬆實現各種動畫型別。在 Flutter 中動畫可以理解為,Widget 在某一段時間內,從一個狀態(大小、位置、顏色、透明度、字型等等)變化到另一個狀態的過程。

Flutter開發中如何選擇動畫

image.png

上圖從 I want a Flutter animation! 出發,最終有四個終點,可以指導我們選擇動畫的實現方式,從上到下複雜程度遞增。

  • Implicit Animations
  • Explicit Animations
  • Low-Level Animations
  • Third-Party Animation Framework

基礎概念

Animation

儲存動畫目前的狀態(例如,是否開始,暫停,前進或倒退)。

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Animation();
  ///...
  /// The current status of this animation.
  AnimationStatus get status;

  /// The current value of the animation.
  @override
  T get value;
複製程式碼

AnimationController

管理 Animation,控制動畫開始、結束等

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
///...  
}
複製程式碼

Curve

字面意思為曲線,可以理解為兩個值的變化曲線,比如勻速、先加速後減速、減速等等

abstract class Curve extends ParametricCurve<double> {
  /// Abstract const constructor to enable subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Curve();
///...
}
複製程式碼

Flutter 內建了多種型別的 Curve。

Curve 各種效果演示

Tween

為動畫物件設定變化範圍值。

class Tween<T extends dynamic> extends Animatable<T> {
  /// Creates a tween.
  ///
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
  Tween({
    this.begin,
    this.end,
  });
}
複製程式碼

Ticker

用來註冊螢幕重新整理的回撥來驅動動畫的執行。

隱式動畫

最簡單的動畫,隱式動畫一般繼承自 ImplicitlyAnimatedWidget,之所以叫隱式動畫,在使用的時候 Flutter 幫我們隱藏了一些細節,我們無需關注 Animation 和 AnimationController 的建立,只需要關注 Widget 從什麼狀態變成了什麼狀態即可。當然隱式動畫並非沒有 Animation 和 AnimationController 只不過在父類 ImplicitlyAnimatedWidget Flutter 幫我們建立好了,ImplicitlyAnimatedWidget 主要程式碼如下:

abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
  const ImplicitlyAnimatedWidget({
    Key key,
    this.curve = Curves.linear,
    @required this.duration,
    this.onEnd,
  }) : assert(curve != null),
       assert(duration != null),
       super(key: key);

  final Curve curve;

  final Duration duration;
  final VoidCallback onEnd;

  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
}

abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
  @protected
  AnimationController get controller => _controller;
  AnimationController _controller;

  /// The animation driving this widget's implicit animations.
  Animation<double> get animation => _animation;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      debugLabel: kDebugMode ? widget.toStringShort() : null,
      vsync: this,
    );
    _controller.addStatusListener((AnimationStatus status) {
      switch (status) {
        case AnimationStatus.completed:
          if (widget.onEnd != null)
            widget.onEnd();
          break;
        case AnimationStatus.dismissed:
        case AnimationStatus.forward:
        case AnimationStatus.reverse:
      }
    });
    _updateCurve();
    _constructTweens();
    didUpdateTweens();
  }
///...
}
複製程式碼

常用的隱式動畫有

  • AnimatedContainer
  • AnimatedAlign
  • AnimatedCrossFade
  • AnimatedDefaultTextStyle
  • AnimatedOpacity
  • AnimatedPadding
  • AnimatedPhysicalModel
  • AnimatedPositioned
  • AnimatedSize
  • AnimatedSwitcher
  • AnimatedTheme

AnimatedContainer

為 Container 新增動畫為例:

    InkWell(
      onTap: () {
        setState(() {
          selected = !selected;
        });
      },
      child: AnimatedContainer(
        color: Colors.blue,
        curve: _curve,
        margin: const EdgeInsets.all(20),
        duration: Duration(seconds: 1),
        width: selected ? 200.0 : 100.0,
        height: selected ? 100.0 : 200.0,
      ),
    ),
複製程式碼

顯式動畫

顯式動畫比隱式動畫稍複雜一點,需要我們指定 Animation 和 AnimationController。顯式動畫一般繼承自 AnimatedWidget

常用的顯式動畫有

  • AlignTransition
  • ScaleTransition
  • SizeTransition
  • SlideTransition
  • PositionedTransition
  • RelativePositionedTransition
class _SizeTransitionDemoState extends State<SizeTransitionDemo>
    with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 3),
      vsync: this,
    );
      // ..repeat();
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.fastOutSlowIn,
    );
    _controller.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SizeTransition"),
        actions: [
          IconButton(icon: Icon(Icons.settings), onPressed: () {}),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            SizeTransition(
              sizeFactor: _animation,
              axis: Axis.horizontal,
              // axisAlignment: -1,
              child: Center(
                child: FlutterLogo(size: 200.0),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

自定義CustomPainter

自定義CustomPainter可以理解為顯示動畫的高階用法,通過自定義畫筆來實現對動畫的更精細控制。

class LinePainter extends CustomPainter {
  final double progress;

  LinePainter(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.yellowAccent
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round;

    Offset startingPoint = Offset(0, size.height / 2);

    Offset endingPoint = Offset(progress, size.height / 2);

    canvas.drawLine(startingPoint, endingPoint, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
複製程式碼

Lottie

用 Lottie 執行設計師設計的複雜動畫,一般為 json 格式。

Lottie.network("https://assets7.lottiefiles.com/packages/lf20_wXxy5o.json", width: 300, height: 300),
Lottie.network("https://assets3.lottiefiles.com/packages/lf20_r9lh4ebq.json", width: 300, height: 300),
Lottie.asset('assets/64586-like-and-dislike-button.json'),
Lottie.asset('assets/65014-dog-walking.json'),
Lottie.asset('assets/65077-breathing-lotus.json'),
複製程式碼

視覺暫留演示

lottie files

相關文章