DBAED6ED-4E11-4344-8500-8F9EA1EA816F_1_105_c.jpeg

隐式动画——Animated系列

1.1 快速开始——AnimatedContainer

//原始main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: Container(		//一个普普通通的Container
        width: 300,
        height: 300,
        color: Colors.blue,
        child: const Center(
          child: Text("Hi",
              style: TextStyle(
                fontSize: 72,
              )),
        ),
      ),
    ));
  }
}
//使用AnimatedContainer,并传入Duration,两行代码就可以动起来!
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: AnimatedContainer(		//将Container换成AnimatedContainer
            duration: const Duration(seconds: 1),	//同时加上duration参数即可
            width: 300,
            height: 100,
            color: Colors.blue,
            child: const Center(
              child: Text("Hi",style: TextStyle(fontSize: 72)),
            ),
          ),
        ),
      ),
    );
  }
}

​ 这里,AnimatedContainer不能管理其child属性的变化,只能在其自身属性变化时产生动画。

​ 值得一提的是,decoration属性是非常好用的,例如:

AnimatedContainer(
  duration: const Duration(seconds: 1),
  width: 300,
  height: 300,
  //color: Colors.blue,			//设置decoration属性后,color属性必须删除
  decoration: BoxDecoration(
    gradient: const LinearGradient(			//设置渐变色
      begin: Alignment.bottomCenter,    //橙色从底部中心开始
      end: Alignment.topCenter,         //白色从顶部中心开始
      colors: [Colors.orange, Colors.white],
      stops: [0.5,0.7],                 //只在这个区间内设置动画效果
    ),
    boxShadow: const [BoxShadow(spreadRadius: 5, blurRadius: 25)],	//边框半径5,阴影半径25
    borderRadius: BorderRadius.circular(150)  //设置300/2=150,出现正圆形;设置0,变为方形
  ),
  child: const Center(
    child: Text("Hi",style: TextStyle(fontSize: 72)),
  ),
),

5514F21B-7194-4DA1-A5D0-165C90F6B1DC_1_105_c

最终效果图如上。

1.2 在不同控件间切换——AnimatedSwitcher

​ AnimatedSwitcher主要监控child属性,如果其child属性发生变化,则会在替换控件时添加过渡动画。如果child变为null,那么该控件将渐渐消失。

​ AnimatedSwitcher根据 1.child的属性 2.key属性 来判断控件是否变化。

AnimatedSwitcher(
  transitionBuilder: (child, animation) {   //自定义动画
    //return FadeTransition(opacity: animation, child: child);  //默认效果:透明度渐变
    //return ScaleTransition(scale: animation, child: child); //缩放动画
    return RotationTransition(turns: animation, child: child); //旋转动画
  },
  duration: const Duration(seconds: 1),	//动画持续1秒
  //child: CircularProgressIndicator(),
  child: Text("h22i",
  	key: UniqueKey(),     //每次都生成一个新的key,因此AnimatedSwitcher每次都会播放切换动画
  	style: const TextStyle(fontSize: 100)
  ),
)

transitionBuilder中,也可以嵌套使用多种动画:

transitionBuilder: (child, animation) {   //自定义动画
  return FadeTransition(   //不透明度渐变 + 旋转动画 的嵌套
    opacity: animation,
    child: RotationTransition(turns: animation, child: child), //旋转动画
  );
},

如果你只有两个控件,要实现来回切换的效果,可以使用优化过后的AnimatedCrossFade控件。

1.3 曲线——Curves.bounceOut

AnimatedOpacity(
	duration: const Duration(seconds: 1),
	curve: Curves.bounceOut,    //设置回弹曲线
	opacity: 0,
	child: Container(width: 300, height: 300, color: Colors.blue),
),

可以多个Animated控件嵌套,例如:

AnimatedPadding(
  duration: const Duration(seconds: 1),
  padding: const EdgeInsets.only(top: 200.0),
  curve: Curves.bounceOut, //设置回弹曲线,默认Curves.linear
  child: AnimatedOpacity(   //AnimatedPadding中嵌套AnimatedOpacity,但动画会一同开始
    duration: const Duration(seconds: 1),
    opacity: 0.5,
    child: Container(width: 300, height: 300, color: Colors.blue),
  ),
),

1.4 Animated还不够?自制补间动画——TweenAnimationBuilder

使用TweenAnimationBuilderTween来创建补间动画。

//使用TweenAnimationBuilder自制一个AnimatedOpacity

TweenAnimationBuilder(
  duration: const Duration(seconds: 1),
  //其中,begin的值只在开始时有效,后期如果修改了end,则会从当前状态(而不是begin)前往新的end
  //从begin到end期间,builder函数会被反复调用,反复渲染新的(含value)的控件
  tween: Tween<double>(begin: 0.0, end: 0.5),   
  //builder是复杂形式的child(如ListBuilder)
  builder: (BuildContext context, double value, Widget? child)=> Opacity(
    opacity: value,	//value也就是tween自动生成的那个值(0.0-0.5之间)
    child: child  //参数中的child不会被反复渲染,而会使用原来的child,达到一个优化的效果
  ),
  child: Container( 
    //这里的child(也就是直属于TweenAnimationBuilder的child)就是builder函数中的child
    //builder不会反复渲染这里的child,达到一个优化的效果
    width: 300,
    height: 300,
    color: Colors.red,
  ),
),

使用Transform来定义变换:

//旋转变换
TweenAnimationBuilder(
  duration: const Duration(seconds: 1),
  tween: Tween<double>(begin: 1.0, end: 6.0),   //忽略begin,则此时begin=end,启动时动画不播放
  builder: (BuildContext context, double value, Widget? child) => Container(
    width: 300,
    height: 300,
    color: Colors.red,
    // * 任何平移旋转缩放操作都能用4*4矩阵表示
    //Transform( transform: Matrix4.identity().translate(x),
    // * 或者使用 Transform.scale缩放 / .rotate旋转(angle:0-2pi) / .translate平移
    child: Center(child: Transform.rotate(	//旋转
      angle: value, 
      child: const Text("Hi",style: TextStyle(fontSize: 70)),
    ),
   ),
  ),
),
//平移变换
TweenAnimationBuilder(
  duration: const Duration(seconds: 1),
  //tween中可以直接放置offset
  tween: Tween(begin: const Offset(0,0), end: const Offset(40,0)),
  builder: (BuildContext context, Offset value, Widget? child) => Container(
    width: 300,
    height: 300,
    color: Colors.red,
    child: Center(child: Transform.translate(//平移操作
      offset: value,
      child: const Text("Hi",style: TextStyle(fontSize: 70),),
    ),
   ),
  ),
),

1.5 计数器动画

实现的效果如下:

6E7223F5-16FA-4D8C-AB06-57CF0EA8E594_1_201_a

代码:

import 'package:flutter/material.dart';
void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
                child: Container(
      width: 300,
      height: 120,
      color: Colors.blue,
      child: const AnimatedCounter(4,duration: Duration(seconds: 1)), //使用滚动计数器
    ))));
  }
}

//滚动计数器
class AnimatedCounter extends StatelessWidget {
  final int number;
  final Duration duration;
  const AnimatedCounter(this.number, {this.duration = const Duration(seconds: 1) ,Key? key})
   : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder(
        duration: duration,
        tween: Tween<double>(end: number.toDouble()), //调整end
        builder: (context, double? value, child) {
          final whole = value! ~/ 1; //整数部分
          final decimal = value - whole;
          return Stack(children: [
            _MyText("$whole", position: -100 * decimal, opacity: 1 - decimal),
            _MyText("${whole + 1}",
                position: 100 - decimal * 100, opacity: decimal),
          ]);
        });
  }
}

class _MyText extends StatelessWidget {
  final String text;
  final double position;
  final double opacity;
  const _MyText(this.text,
      {required this.position, required this.opacity, Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned(
      child: Opacity(
        opacity: opacity,
        child: Text(text, style: const TextStyle(fontSize: 100)),
      ),
      top: position,
    );
  }
}