继续创造,加速成长!这是我参与「日新计划 10 月更文挑战」的第28天,点击检查活动概况

概述

除Hero, AnimatedWidget外,还有很多动画作用,如组件过度动画,物理动画,隐式动画,显式动画,交错动画等。但实质都是在一段时刻内不断改动屏幕上显现的内容,然后产生视觉暂留现象。

动画一般可分为两类:

[补间动画]:补间动画是一种预先界说物体运动的起点和结尾,物体的运动方式,运动时刻,时刻曲线,然后从起点过渡到结尾的动画。

「基于物理的动画」:基于物理的动画是一种模仿实际国际运动的动画,经过树立运动模型来完成。例如一个篮球 从高处落下,需求依据其下落高度,重力加速度,地上反弹力等影响因素来树立运动模型。

隐式动画

隐式动画运用 Flutter 结构内置的动画部件创立,经过设置动画的起始值和最终值来触发。当运用 setState 办法改动部件的动画特点值时,结构会主动计算出一个从旧值过渡到新值的动画。

比如 AnimatedOpacity 部件,改动它的 opacity 值就能够触发动画。

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画

代码:

class OpacityChangePage extends StatefulWidget {
  const OpacityChangePage({Key? key}) : super(key: key);
  @override
  State<OpacityChangePage> createState() => _OpacityChangePageState();
}
class _OpacityChangePageState extends State<OpacityChangePage> {
  double _opacity = 1.0;
  //改动目标值
  void _toggle(){
    _opacity = _opacity >0 ?0.0:1.0;
    setState(() {
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar("隐式动画"),
      body: Center(
        child: AnimatedOpacity(opacity: _opacity, duration: Duration(seconds: 2),
          child: Container(width: 200,height: 200,color: Colors.yellow,),
        )
      ),
      floatingActionButton: FloatingActionButton(onPressed: _toggle,child: Icon(Icons.play_arrow),),
    );
  }
}

显式动画

显式动画指的是需求手动设置动画的时刻,运动曲线,取值规模的动画。将值传递给动画部件如: RotationTransition,最后运用一个AnimationController 操控动画的开端和完毕。

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画
代码:

import 'dart:math';
import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 09:03
/// @Description: 
class RotationAnimationPage extends StatefulWidget {
  const RotationAnimationPage({Key? key}) : super(key: key);
  @override
  State<RotationAnimationPage> createState() => _RotationAnimationPageState();
}
class _RotationAnimationPageState extends State<RotationAnimationPage> with SingleTickerProviderStateMixin{
  late AnimationController _controller;
  late Animation<double> _turns;
  bool _playing = false;
  //操控动画状况
  void _toggle(){
    if(_playing){
      _playing = false;
      _controller.stop();
    }else{
      _controller.forward()..whenComplete(() => _controller.reverse());
      _playing = true;
    }
    setState(() {
    });
  }
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //初始化动画操控器,设置动画时刻
    _controller = AnimationController(vsync: this,duration: Duration(seconds: 10));
    //设置动画取值规模和时刻曲线
    _turns = Tween(begin: 0.0, end: pi * 2).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );
  }
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _controller.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar("显现动画"),
      body: Center(
        child: RotationTransition(
          turns: _turns,
          child:  Container(
            width: 200,
            height: 200,
            child: Image.asset("images/室内_电扇02.png",fit: BoxFit.cover,),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(onPressed: _toggle,
      child: Icon(_playing ? Icons.pause : Icons.play_arrow),
      ),
    );
  }
}

除了 RotationTransition 外,还有其他的显现动画部件如:FadeTransition, ScaleTransition, SizeTransition, SlideTransition 等。

交错动画

交错动画是由一系列的小动画组成的动画。每个小动画可所以接连或间断的,也能够相互重叠。其要害点在于运用 Interval 部件给每个小动画设置一个时刻距离,以及为每个动画的设置一个取值规模 Tween,最后运用一个 AnimationController 操控整体的动画状况。

Interval 承继至 Curve 类,经过设置特点 beginend 来确认这个小动画的运转规模。

class Interval extends Curve {
  /// 动画起始点
  final double begin;
  /// 动画完毕点
  final double end;
  /// 动画缓动曲线
  final Curve curve;
  /// ...
}

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画

这是一个由 5 个小动画组成的交错动画,宽度,高度,颜色,圆角,边框,每个动画都有自己的动画区间。

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画
代码:

import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 09:53
/// @Description: 
class StaggeredAnimationPage extends StatefulWidget {
  const StaggeredAnimationPage({Key? key}) : super(key: key);
  @override
  State<StaggeredAnimationPage> createState() => _StaggeredAnimationPageState();
}
class _StaggeredAnimationPageState extends State<StaggeredAnimationPage> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _width;
  late Animation<double> _height;
  late Animation<Color> _color;
  late Animation<double> _border;
  late Animation<BorderRadius> _borderRadius;
 void _play(){
   if(_controller.isCompleted){
     _controller.reverse();
   }else{
     _controller.forward();
   }
 }
 @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = AnimationController(vsync: this,
    duration: Duration(seconds:5)
    );
    //宽度改动
    _width = Tween<double>(begin: 100,end: 300).animate(CurvedAnimation(
      parent: _controller,
      curve:Interval(0.0, 0.2,curve: Curves.ease),
    ));
    //高度改动
   _height = Tween<double>(begin: 100,end: 300).animate(
     CurvedAnimation(
       parent: _controller,
       curve: Interval(0.2,0.4,curve: Curves.ease)
     )
   );
   //颜色改动
    _color = Tween(begin: Colors.blue,end: Colors.yellow).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.4,0.6,curve: Curves.ease)
      )
    ) ;
    _borderRadius = Tween(
      begin: BorderRadius.circular(0.0),
      end: BorderRadius.circular(150.0),
    ).animate(CurvedAnimation(parent: _controller,
        curve: Interval(0.6,0.8,curve: Curves.ease)
    )) ;
    _border = Tween<double>(
      begin: 0,
      end: 25,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.8, 1.0),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('交错动画')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (BuildContext context, Widget? child) {
            return Container(
              width: _width.value,
              height: _height.value,
              decoration: BoxDecoration(
                color: _color.value,
                borderRadius: _borderRadius.value,
                border: Border.all(
                  width: _border.value,
                  color: Colors.cyanAccent
                )
              ),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _play,
        child: Icon(Icons.refresh),
      ),
    );
  }
}

履行进程报错:Cannot lerp between “MaterialColor(primary value: Color(0xff2196f3))” and “MaterialColor(primary value: Color(0xffffeb3b))”.

To lerp colors, consider ColorTween instead.

物理动画

物理动画是一种模仿实际国际物体运动的动画。需求树立物体的运动模型,以一个物体下落为例,这个运动受到物体的下落高度,重力加速度,地上的反作用力等因素的影响。

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画
代码:

import 'package:demo202112/utils/common_appbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
/// @Author wywinstonwy
/// @Date 2022/10/25 11:16
/// @Description:
class ThrowAnimationPage extends StatefulWidget {
  const ThrowAnimationPage({Key? key}) : super(key: key);
  @override
  State<ThrowAnimationPage> createState() => _ThrowAnimationPageState();
}
class _ThrowAnimationPageState extends State<ThrowAnimationPage> {
  // 球心高度
  double y = 70.0;
  // Y 轴速度
  double vy = -10.0;
  // 重力
  double gravity = 0.1;
  // 地上反弹力
  double bounce = -0.5;
  // 球的半径
  double radius = 50.0;
  // 地上高度
  final double height = 700;
  void _fall(_) {
    y += vy;
    vy += gravity;
    //假如球体触及地上,依据地上反弹力改动球体的 Y 轴速度
    if (y + radius > height) {
      y = height - radius;
      vy *= bounce;
    } else if (y - radius < 0) {
      y = 0 + radius;
      vy *= bounce;
    }
    setState(() {});
  }
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // 运用一个 Ticker 在每次更新界面时运转球体下落办法
    Ticker(_fall)..start();
  }
  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      appBar: AppBar(title: Text('物理动画')),
      body: Column(
        children: <Widget>[
          Container(
            height: height,
            child: Stack(
              children: <Widget>[
                Positioned(
                  top: y - radius,
                  left: screenWidth / 2 - radius,
                  child: Container(
                    width: radius * 2,
                    height: radius * 2,
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      shape: BoxShape.circle,
                    ),
                  ),
                ),
              ],
            ),
          ),
          Expanded(child: Container(color: Colors.blue)),
        ],
      ),
    );
  }
}

组件过度动画

Widget特点发生改动时会履行过渡动画的组件统称为”动画过渡组件“,而动画过渡组件最显着的一个特征就是它会在内部自管理AnimationController。咱们知道,为了方便运用者能够自界说动画的曲线、履行时长、方向等,在前面介绍过的动画封装办法中,通常都需求运用者自己提供一个AnimationController目标来自界说这些特点值。可是,如此一来,运用者就必须得手动管理AnimationController,这又会添加运用的复杂性。因而,假如也能将AnimationController进行封装,则会大大提高动画组件的易用性。

咱们要完成一个AnimatedDecoratedBox,它能够在decoration特点发生改动时,从旧状况变成新状况的进程能够履行一个过渡动画。依据前面所学的常识,咱们完成了一个AnimatedDecoratedBox1组件:

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画

/// @Author wywinstonwy
/// @Date 2022/10/16 10:15 上午
/// @Description: 
import "package:flutter/material.dart";
class AnimatedDecoratedBox1 extends StatefulWidget {
  final BoxDecoration decoration;
  final Widget child;
  final Duration duration;
  final Curve ?curve;
  final Duration? reverseDuration;
  const AnimatedDecoratedBox1({
    required this.decoration,
    required this.child,
    required this.duration,
     this.curve,
      this.reverseDuration});
  @override
  _AnimatedDecoratedBox1State createState() => _AnimatedDecoratedBox1State();
}
class _AnimatedDecoratedBox1State extends State<AnimatedDecoratedBox1> with SingleTickerProviderStateMixin{
  AnimationController get controller=>_controller;
  late AnimationController _controller;
  late Animation<double> _animation;
  late DecorationTween _tween;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      reverseDuration: widget.reverseDuration,
      vsync: this
    );
    _tween = DecorationTween(begin: widget.decoration);
    _updateCurve();
  }
  void _updateCurve(){
    _animation = CurvedAnimation(parent: _controller, curve: widget.curve!);
  }
  @override
  void didUpdateWidget(covariant AnimatedDecoratedBox1 oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(widget.curve != oldWidget) _updateCurve();
    _controller.duration = widget.duration;
    _controller.reverseDuration = widget.reverseDuration;
    //正在履行过度动画
    if(widget.decoration !=(_tween.end??_tween.begin)){
      _tween
      ..begin =_tween.evaluate(_animation)
      ..end = widget.decoration;
      _controller
      ..value=0.0
      ..forward();
    }
  }
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: (BuildContext context, Widget? child) {
        return DecoratedBox(
            decoration: _tween.animate(_animation).value);
      },
      animation: _animation,
      child:widget.child,
    );
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

下面咱们来运用AnimatedDecoratedBox1来完成按钮点击后背风光从蓝色过渡到红色的作用:

class MyExcessiveAnimation extends StatefulWidget {
  const MyExcessiveAnimation({Key? key}) : super(key: key);
  @override
  _MyExcessiveAnimationState createState() => _MyExcessiveAnimationState();
}
class _MyExcessiveAnimationState extends State<MyExcessiveAnimation> {
  Color _decorationColor = Colors.blue;
  var duration = Duration(seconds: 1);
  // Curve curve = CurvedAnimation(parent: ) as Curve;
  // Tween doubleTween =  Tween<double>(begin: -200.0, end: 0.0);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: getAppBar("组件过度动画"),
      body: Column(children: [
        ElevatedButton(onPressed: (){
          setState(() {
            _decorationColor = Colors.red;
          });
        }, child: const Text('测试')),
        Container(height: 44,
          width: 200,
          child: AnimatedDecoratedBox1(
          decoration: BoxDecoration(color: _decorationColor),
          child: ElevatedButton(onPressed: (){
            setState(() {
              _decorationColor = Colors.red;
            });
          },
              child: const Text('AnimatedDecoratedBox',
                style: TextStyle(color: Colors.black,fontSize: 14),
              )
          ),
          duration: duration,
          curve: Curves.linear,
        ),
        )
      ],)
      ,
    );
  }
}

总结

本文介绍了 Flutter 中多种类型的动画,分别是

  • 隐式动画
  • 显式动画
  • 交错动画
  • 基于物理的动画
  • 组件过度动画

Flutter 动画基于类型化的 Animation 目标,Widgets 经过读取动画目标的当时值和监听状况改动重新运转 build 函数,不断改动 UI 形成动画作用。 一个动画的主要因素有

  • Animation 动画目标
  • AnimationController 动画操控器
  • Tween 动画取值规模
  • Curve 动画运动曲线

更多的动画能够参考官方文档:api.flutter.dev/flutter/ani…

28、Flutter之组件过度动画,物理动画,隐式动画,显式动画,交织动画