Flutter部件学习之动画(animation)

Animation

  • Animation是Flutter 动画库中的核心类,插入用于指导动画的值。

  • Animation 对象知道动画目前的状态(例如,是否开始,暂停,前进或倒退),但是对屏幕上显示的内容一无所知。

  • Animation 对象与渲染或 build() 函数无关。

  • Animation 对象具有状态。它的当前值在 .value 中始终可用。

  • 动画还可以插入除 double 以外的类型,比如 Animation<Color> 或者 Animation<Size>

AnimationController

AnimationController  是动画控制器,是一个特殊的 Animation 对象,派生自Animation<double>,管理着 Animation,所以可以用在任何需要 Animation 对象的地方。

AnimationController用于控制动画,它包含动画的启动forward()停止stop() 反向播放 reverse()等方法。

AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(lowerBound != null),
       assert(upperBound != null),
       assert(upperBound >= lowerBound),
       assert(vsync != null),
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value ?? lowerBound);
  }
  • duration表示动画执行的时长,通过它我们可以控制动画的速度。
  • vsync: 当创建一个 AnimationController 时必须要传递一个 vsync参数,可以防止屏幕外动画消耗不必要资源。单个AnimationController使用SingleTickerProviderStateMixin ,多个AnimationController使用TickerProviderStateMixin 

创建 AnimationController 的同时,也赋予了一个 vsync 参数。 vsync 的存在防止后台动画消耗不必要的资源。

AnimationController的初始化常常在 statefulWidget 中进行。

AnimationController.value就是当前动画的值。

CurvedAnimation

CurvedAnimation 动画执行曲线是Animation的一个实现类,它的目的是为了给AnimationController增加动画曲线。

  • 动画过程可以是匀速的、匀加速的或者先加速后减速等。
  • Flutter中通过Curve(曲线)来描述动画过程,我们把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。
  • CurvedAnimation可以通过包装AnimationControllerCurve生成一个新的动画对象 

CurvedAnimation({
    required this.parent,
    required this.curve,
    this.reverseCurve,
  }) : assert(parent != null),
       assert(curve != null) {
    _updateCurveDirection(parent.status);
    parent.addStatusListener(_updateCurveDirection);
  }

 /// The animation to which this animation applies a curve.
  @override
  final Animation<double> parent;
animationConroller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 50), //变化时间
      lowerBound: 0.0, //被忽略值
      upperBound: 1.0, //已完成值
    );
    animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);

Tween

Tween 插值器,这是为动画提供起始值和结束值的一个对象。在flutter中,动画其实就是从起始值到结束值的一个过渡过程。

  • Tween 为动画对象插入一个范围值。
  • Tween 对象不存储任何状态。而是提供 evaluate(Animation<double> animation) 方法,将映射函数应用于动画当前值。 Animation 对象的当前值可以在 .value 方法中找到。 evaluate 函数还执行一些内部处理内容,比如确保当动画值在 0.0 和1.0 时分别返回起始点和终点。
  • 要使用 Tween 对象仅仅时映射,动画的控制依旧是在AnimationController控制。因此需要 Tween 调用 animate()传入控制器对象。
  • 默认情况下,Flutter 会将起始值和结束值分别定义为double类型的0.0和1.0.

除了通过泛型自定义插值类型之外,Flutter还提供了很多具体类型的插值器。如:ColorTween

    animation = Tween<double>(begin: 390.0, end: 400.0).animate(animationConroller);

动画通知

一个 Animation 对象可以有多个 Listener 和 StatusListener,用 addListener()和 addStatusListener() 来定义。

  • addListener();它可以用于给Animation添加帧监听器,在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
  • addStatusListener();它可以给Animation添加“动画状态改变”监听器;动画开始、结束、正向或反向。

动画的状态

dimissed:动画停在开始处

completed:动画停在结束出

forward:动画正在从开始处运行到结束处(正向)

reverse:动画正在从结束出运行到开始处(反向)

动画的控制方法

  • forward:正向执行动画
  • reverse:反向执行动画
  • repeat:反复执行动画
  • reset:重置动画

forward和reverse方法中都有from参数,表示动画从 from 值开始执行,而不是从lowerBound到upperBound.

实例: 

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

class AnimationPage extends StatefulWidget {
  @override
  State<AnimationPage> createState() => _AnimationPageState();
}

class _AnimationPageState extends State<AnimationPage> with SingleTickerProviderStateMixin {

  late AnimationController animationConroller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    animationConroller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 50), //变化时间
      lowerBound: 0.0, //被忽略值
      upperBound: 1.0, //已完成值
    );
    animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
    animation = Tween(begin: 390.0, end: 400.0).animate(animation);
    animation.addListener(() {
      setState(()=>{});
    });
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //当动画完成时
        animationConroller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //当动画未完成时,继续向前执行
        animationConroller.forward();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("动画"),
      ),
      body: Center(
        child: Container(
          child: Icon(
            Icons.favorite,
            color: Colors.red,
            size: animation.value,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          print("执行动画");
          //_controller.forward();//向前执行动画
          /*点击按钮暂停和开始*/
          if (animationConroller.isAnimating) //如果正在执行动画
            animationConroller.stop(); //则暂停
          else
            animationConroller.forward();
        },
      ),
    );
  }


}

个人理解:

AnimationController是控制动画的核心,它定义了动画的执行时间、起始值和结束值等属性。

后面的Animation对象则是用来描述动画的变化过程,例如动画的曲线、插值方式、目标值等。

这些Animation对象都是基于AnimationController的,它们继承了AnimationController的控制,同时添加了更多对动画的描述。

使用AnimatedWidget简化

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
//使用AnimatedWidget简化

class AnimationWithWidget extends StatefulWidget {
  const AnimationWithWidget({Key? key}) : super(key: key);

  @override
  State<AnimationWithWidget> createState() => _AnimationWithWidgetState();
}

class _AnimationWithWidgetState extends State<AnimationWithWidget> with SingleTickerProviderStateMixin{

  late AnimationController animationConroller;
  late Animation<double> animation;
  @override
  void initState() {
    super.initState();
    animationConroller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 50), //变化时间
      lowerBound: 0.0, //被忽略值
      upperBound: 1.0, //已完成值
    );
    animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
    animation = Tween(begin: 390.0, end: 400.0).animate(animation);
    animation.addListener(() {
      setState(()=>{});
    });
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //当动画完成时
        animationConroller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //当动画未完成时,继续向前执行
        animationConroller.forward();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("动画_使用AnimatedWidget简化"),
      ),
      body:animationOne(animation:animation ,) ,
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          print("执行动画");
          //_controller.forward();//向前执行动画
          /*点击按钮暂停和开始*/
          if (animationConroller.isAnimating) //如果正在执行动画
            animationConroller.stop(); //则暂停
          else
            animationConroller.forward();
        },
      ),
    );
  }
}

class animationOne extends AnimatedWidget{

  const animationOne({super.key, required Animation<double> animation})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        child: Icon(
          Icons.favorite,
          color: Colors.red,
          size: animation.value,
        ),
      ),
    );
  }
}

用AnimatedBuilder重构

将渲染逻辑分离出来 

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

class AnimationWithBuilder extends StatefulWidget {

  @override
  State<AnimationWithBuilder> createState() => _State();
}

class _State extends State<AnimationWithBuilder> with SingleTickerProviderStateMixin {
  late AnimationController animationConroller;
  late Animation<double> animation;
  @override
  void initState() {
    super.initState();
    animationConroller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 50), //变化时间
      lowerBound: 0.0, //被忽略值
      upperBound: 1.0, //已完成值
    );
    animation = CurvedAnimation(parent: animationConroller, curve: Curves.bounceIn);
    animation = Tween(begin: 390.0, end: 400.0).animate(animation);
    animation.addListener(() {
      setState(()=>{});
    });
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //当动画完成时
        animationConroller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //当动画未完成时,继续向前执行
        animationConroller.forward();
      }
    });
  }


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("动画_使用AnimatedBuilder简化"),
      ),
      body:BuilderAnimation(animation) ,
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          print("执行动画");
          /*点击按钮暂停和开始*/
          if (animationConroller.isAnimating) //如果正在执行动画
            animationConroller.stop(); //则暂停
          else
            animationConroller.forward();
        },
      ),
    );
  }
}

class BuilderAnimation extends StatelessWidget {
  final Animation<double> animation;

  BuilderAnimation(this.animation);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: animation,
        child:Icon(
          Icons.favorite,
          color: Colors.red,
          size: animation.value,
        ) ,
        builder: (context,child){
          return child!;
        });
  }
}

Hero动画

在 Flutter 中,图像从当前页面转到另一个页面称为 hero 动画,相同的动作有时也被称为 共享元素过渡。 

const Hero({
    Key? key,
    required this.tag,
    this.createRectTween,
    this.flightShuttleBuilder,
    this.placeholderBuilder,
    this.transitionOnUserGestures = false,
    required this.child,
  }) : assert(tag != null),
       assert(transitionOnUserGestures != null),
       assert(child != null),
       super(key: key);
  • tag, 在不同页面分别使用两个 hero widgets,同时使用配对的标签来实现动画。
  • 通过推送目标页面到 Navigator 堆栈来触发动画。 Navigator 推送并弹出操作触发原页面和目标页面中含有配对标签 heroes 的 hero 动画。

  • Flutter 设置了 tween 用来界定 Hero 从起点到终点的界限(插入大小和位置),并在图层上执行动画。

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

//跳转后的照片类集合
class Photo extends StatelessWidget {
  const Photo({super.key, required this.photo, this.onTap});

  final String photo;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context) {
    return Material(
      // Slightly opaque color appears where the image has transparency.
      color: Theme.of(context).primaryColor.withOpacity(0.25),
      child: InkWell(
        onTap: onTap,
        child: LayoutBuilder(
          builder: (context, size) {
            return Image.asset(
              photo,
              fit: BoxFit.contain,
            );
          },
        ),
      ),
    );
  }
}

class RadialExpansion extends StatelessWidget {
  const RadialExpansion({
    super.key,
    required this.maxRadius,
    this.child,
  }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);

  final double maxRadius;
  final double clipRectSize;
  final Widget? child;

  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          child: ClipRect(
            child: child,
          ),
        ),
      ),
    );
  }
}

class RadialExpansionDemo extends StatelessWidget {
  const RadialExpansionDemo({super.key});

  static double kMinRadius = 32.0;
  static double kMaxRadius = 128.0;
  static Interval opacityCurve =
      const Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);

  //Rect.fromLTRB(10, 20, 20, 10)
  static RectTween _createRectTween(Rect? begin, Rect? end) {
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 5.0; // 1.0 is normal animation speed.

    return Scaffold(
      appBar: AppBar(
        title: const Text('Radial Transition Demo'),
      ),
      body: Container(
        padding: const EdgeInsets.all(32),
        alignment: FractionalOffset.bottomLeft,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            _buildHero(context, 'assets/tabbar/group_active.png', 'Chair'),
            _buildHero(context, 'assets/tabbar/home_active.png', 'Binoculars'),
            _buildHero(context, 'assets/tabbar/mall_active.png', 'Beach ball'),
          ],
        ),
      ),
    );
  }

  // 底部导航栏
  Widget _buildHero(BuildContext context, String imageName, String description) {
    return SizedBox(
      width: kMinRadius * 2.0,
      height: kMinRadius * 2.0,
      child: Hero(
        createRectTween: _createRectTween,
        tag: imageName,
        child: RadialExpansion(
          maxRadius: kMaxRadius,
          child: Photo(
            photo: imageName,
            onTap: () {
              Navigator.of(context).push(
                PageRouteBuilder<void>(
                  pageBuilder: (context, animation, secondaryAnimation) {
                    return AnimatedBuilder(
                        animation: animation,
                        builder: (context, child) {
                          return Opacity(
                            opacity: opacityCurve.transform(animation.value),
                            child: _buildPage(context, imageName, description),
                          );
                        });
                  },
                ),
              );
            },
          ),
        ),
      ),
    );
  }

  //点击导航栏item后跳转的页面
  static Widget _buildPage(BuildContext context, String imageName, String description) {
    return Container(
      color: Theme.of(context).canvasColor,
      child: Center(
        child: Card(
          elevation: 8,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                width: kMaxRadius * 2.0,
                height: kMaxRadius * 2.0,
                child: Hero(
                  createRectTween: _createRectTween,
                  tag: imageName,
                  child: RadialExpansion(
                    maxRadius: kMaxRadius,
                    child: Photo(
                      photo: imageName,
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                    ),
                  ),
                ),
              ),
              Text(
                description,
                style: const TextStyle(fontWeight: FontWeight.bold),
                textScaleFactor: 3,
              ),
              const SizedBox(height: 16),
            ],
          ),
        ),
      ),
    );
  }
}

/*
void main() {
  runApp(
    const MaterialApp(
      home: RadialExpansionDemo(),
    ),
  );
}*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值