Flutter:动画

前言

学习参考:老孟 flutter动画
基本上开发时使用的组件都有其动画,关于动画方面的知识,一般情况很少会用到。因此这里只学习关于动画的基本知识。

AnimationController

Flutter中的AnimationController是一个用于控制动画的类。它可以控制动画的开始、停止、反转、重置等操作,并且可以设置动画的持续时间、曲线等属性。

AnimationController 通常在 initState 方法中初始化,在dispose中释放动画

使用AnimationController需要先创建一个实例,然后设置动画的持续时间、曲线等属性,最后通过调用forward()方法来启动动画。在动画运行过程中,可以通过调用reverse()方法来反转动画,通过调用stop()方法来停止动画,通过调用reset()方法来重置动画。

AnimationController还可以添加监听器,用于监听动画的状态变化。例如,可以通过添加addListener()方法来监听动画的值变化,从而更新UI界面。通过添加addStatusListener来监听动画的状态

// 单个 AnimationController 的时候使用 SingleTickerProviderStateMixin,多个 AnimationController 使用 TickerProviderStateMixin。
class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  double size = 100;
  // 定义动画控制器对象
  late AnimationController _controller;

  // AnimationController 通常在 initState 方法中初始化
  
  void initState() {
    
    
    // TODO: implement initState
    super.initState();
    // vsync 用于防止屏幕外动画消耗不必要的资源
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    // 监听动画帧的变化,在每一帧中调用setState来更新UI,AnimationController 的值默认是 0 到 1
    _controller.addListener(() {
    
    
      setState(() {
    
    
        // 使size从100到200
        size = 100 + 100 * _controller.value;
      });
    });
    
    //  监听动画的状态,当动画正序完成后反向执行动画
    _controller.addStatusListener((status) {
    
    
      // 动画状态status的值有:dismissed(动画停止在开始处)、forward(正向运行)、reverse(反向运行)、completed(动画停止在结束处)
      if (status == AnimationStatus.completed) {
    
    
        _controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
    
    
        _controller.forward();
      }
    });
  }

  
  void dispose() {
    
    
    super.dispose();
    //释放动画
    _controller.dispose();
  }

  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
      onTap: () {
    
    
        // 启动动画
        _controller.forward();
      },
      child: Container(
        width: size,
        height: size,
        color: Colors.blue,
        alignment: Alignment.center, // 设置文字居中
        child: const Text("点击变大"),
      ),
    ));
  }
}

在这里插入图片描述

Tween

Flutter中的Tween是用于在动画中定义起始值和结束值之间的插值计算的类。它可以将一个范围内的值映射到另一个范围内的值,从而实现动画效果

上面的案例可以修改为

class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  double size = 100;
  // 定义动画控制器对象
  late AnimationController _controller;
  // 定义一个动画对象
  late Animation _animation;

  // AnimationController 通常在 initState 方法中初始化
  
  void initState() {
    
    
    // TODO: implement initState
    super.initState();
    // vsync 用于防止屏幕外动画消耗不必要的资源
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    final Tween tween = Tween(begin: 100.0, end: 200.0);
    _animation = tween.animate(_controller);
    _animation.addListener(() {
    
    
      setState(() {
    
    
        size = _animation.value;
      });
    });
  }

  
  void dispose() {
    
    
    super.dispose();
    //释放动画
    _controller.dispose();
  }

  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
      onTap: () {
    
    
        // 启动动画
        _controller.forward();
      },
      child: Container(
        width: size,
        height: size,
        color: Colors.blue,
        alignment: Alignment.center, // 设置文字居中
        child: const Text("点击变大"),
      ),
    ));
  }
}

同理也可以改变颜色

class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  Color _color = Colors.blue;
  // 定义动画控制器对象
  late AnimationController _controller;
  // 定义一个动画对象
  late Animation _animation;

  // AnimationController 通常在 initState 方法中初始化
  
  void initState() {
    
    
    // TODO: implement initState
    super.initState();
    // vsync 用于防止屏幕外动画消耗不必要的资源
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    final Tween tween = ColorTween(begin: Colors.blue, end: Colors.red);
    _animation = tween.animate(_controller);
    _animation.addListener(() {
    
    
      setState(() {
    
    
        _color = _animation.value;
      });
    });
  }

  
  void dispose() {
    
    
    super.dispose();
    //释放动画
    _controller.dispose();
  }

  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
      onTap: () {
    
    
        // 启动动画
        _controller.forward();
      },
      child: Container(
        width: 100,
        height: 100,
        color: _color,
        alignment: Alignment.center, // 设置文字居中
        child: const Text("点击改变颜色"),
      ),
    ));
  }
}

在这里插入图片描述

Curve

动画中还有一个重要的概念就是 Curve,即动画执行曲线。使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。

// 使用了 chain 方法将 ColorTween 和 CurveTween 组合起来
  final Animatable<Color?> tween =
      ColorTween(begin: Colors.blue, end: Colors.red)
          .chain(CurveTween(curve: Curves.easeInOut));
  _animation = tween.animate(_controller);
  _animation.addListener(() {
    
    
    setState(() {
    
    
      _color = _animation.value;
    });
  });

动画组件

Flutter 系统提供了20多个动画组件,这些组件都是基于动画的核心知识实现的。动画组件分为两大类:

  • 隐式动画组件:只需提供给组件动画开始、结束值,组件创建 AnimationController、Curve、Tween,执行动画,释放AnimationController
  • 显式动画组件:需要设置 AnimationController,控制动画的执行,使用显式动画可以完成任何隐式动画的效果,甚至功能更丰富一些,不过你需要管理该动画的 AnimationController 生命周期
  • 显示动画组件和隐式动画组件中各有一个万能的组件,它们是 AnimatedBuilder 和 TweenAnimationBuilder,当系统中不存在我们想要的动画组件时,可以使用这两个组件

隐式动画组件的使用

class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  double _opacity = 1.0;
  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
            onTap: () {
    
    
              setState(() {
    
    
                _opacity = 0;
              });
            },
            child: AnimatedOpacity(
              opacity: _opacity,
              duration: const Duration(seconds: 1),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
                alignment: Alignment.center, // 设置文字居中
                child: const Text("点击改变颜色"),
              ),
            )));
  }
}

在这里插入图片描述
**TweenAnimationBuilder **
TweenAnimationBuilder 是 Flutter 中的一个动画构建器,可以用于创建一个在两个值之间进行动画的动画组件。使用 TweenAnimationBuilder 需要指定两个值之间的插值器(Tween),以及动画的持续时间和动画结束后的回调函数。

class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  // 一开始不能定义null,否则运行会报错
  late ColorTween _tween = ColorTween(begin: Colors.blue, end: Colors.blue);
  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
            onTap: () {
    
    
              setState(() {
    
    
                _tween = ColorTween(begin: Colors.blue, end: Colors.red);
              });
            },
            child: TweenAnimationBuilder(
              duration: const Duration(seconds: 1),
              tween: _tween,
              builder: (BuildContext context, Color? value, Widget? child) {
    
    
                return Container(
                  width: 100,
                  height: 100,
                  color: value,
                  alignment: Alignment.center, // 设置文字居中
                  child: const Text("点击改变颜色"),
                );
              },
            )));
  }
}
class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  //  动画控制器
  late AnimationController _controller;
  // 颜色动画
  late Animation _colorAnimation;
  // 大小动画
  late Animation _sizeAnimation;

  
  void initState() {
    
    
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
    // 使用AnimationController的drive方法将一个Tween对象与AnimationController关联起来
    _colorAnimation =
        _controller.drive(ColorTween(begin: Colors.blue, end: Colors.red));
    // 使用AnimationController的drive方法将一个Tween对象与AnimationController关联起来
    _sizeAnimation = _controller
        .drive(SizeTween(begin: const Size(100, 50), end: const Size(50, 100)));
    super.initState();
  }

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

  
  Widget build(BuildContext context) {
    
    
    return Center(
        //创建一个手势识别器
        child: GestureDetector(
            onTap: () {
    
    
              setState(() {
    
    
                // 开始动画
                _controller.forward();
              });
            },
            child: AnimatedBuilder(
                animation: _controller,
                builder: (context, widget) {
    
    
                  return Container(
                    width: _sizeAnimation.value.width,
                    height: _sizeAnimation.value.height,
                    color: _colorAnimation.value,
                    alignment: Alignment.center, // 设置文字居中
                    child: const Text("点击改变颜色"),
                  );
                })));
  }
}

在这里插入图片描述

列表动画

AnimatedList提供了一种简单的方式使列表数据发生变化时加入过渡动画

AnimatedList主要属性如下表。

属性 说明
itemBuilder 一个函数,列表的每一个索引会调用,这个函数有一个animation参数,可以设置成任何一个动画
initialItemCount item的个数
scrollDirection 滚动方向,默认垂直
controller scroll控制器

列表数据的插入和删除有进出场动画需要调用AnimatedListState指定的方法,只删除原数据并调用setState方法是没有动画效果的

class _YcHomeBodyState extends State<YcHomeBody>
    with SingleTickerProviderStateMixin {
    
    
  // 定义一个全局的key来管理AnimatedListState对象,并将其传递给AnimatedList构造函数
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  // 定义列表
  final List<String> _itemList = ["item 1", "item 2", "item 3"];

  // 新增
  void addItem() {
    
    
    _itemList.add('item ${
      
      _itemList.length + 1}');
    _listKey.currentState?.insertItem(_itemList.length - 1);
  }

  // 删除
  void removeItem() {
    
    
    // 删除操作要注意,要先删除列表中的数据在删除AnimatedListState的状态
    // 并且关于index下标的操作,要放在删除操作之前,不然会导致删除时下标错误报错
    int index = _itemList.length - 1;
    String title = _itemList[index];
    _itemList.removeAt(index);
    _listKey.currentState?.removeItem(
      index,
      (context, animation) => SlideTransition(
        position: animation.drive(CurveTween(curve: Curves.easeIn)).drive(
            Tween<Offset>(begin: const Offset(1, 1), end: const Offset(0, 1))),
        child: Card(
          child: ListTile(
            title: Text(title),
          ),
        ),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    
    
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(onPressed: addItem, child: const Text('增加')),
            ElevatedButton(onPressed: removeItem, child: const Text('减少')),
          ],
        ),
        // AnimatedList需要有高度
        Expanded(
            child: AnimatedList(
                key: _listKey,
                initialItemCount: _itemList.length, // item的个数
                itemBuilder: (context, index, animation) {
    
    
                  // 为每一个item设置动画,将曲线动画和平移动画结合在一起
                  return SlideTransition(
                    // 动画对象,用于控制子组件的平移动画
                    position: animation
                        .drive(CurveTween(curve: Curves.easeIn))
                        .drive(Tween<Offset>(
                            begin: const Offset(1, 1),
                            end: const Offset(0, 1))),
                    child: Card(
                      child: ListTile(
                        title: Text(_itemList[index]),
                      ),
                    ),
                  );
                }))
      ],
    );
  }
}

这个东西挺难搞的,出了不少问题。删除时一点要注意:

  • 删除操作要注意,要先删除列表中的数据在删除AnimatedListState的状态
  • 并且关于index下标的操作,要放在删除操作之前,不然会导致删除时下标错误报错

关于新增时简单,删除时复杂我查到的解释是:

在AnimatedList中,新增操作只需要添加数据到列表中并调用AnimatedListState的insertItem方法即可,AnimatedListState会自动处理动画效果。
但是删除操作需要手动处理动画效果,因为AnimatedListState无法自动处理删除动画。因此,需要手动调用AnimatedListState的removeItem方法,并在其中指定删除动画的实现方式。

在这里插入图片描述

Hero

Hero用于在两个页面之间实现平滑的过渡效果。它可以将一个widget从一个页面转换到另一个页面,同时保持其外观和位置不
变。
Hero动画通常用于在两个页面之间传递图像或其他媒体内容时,可以使用户感觉到这些内容在两个页面之间平滑地移动。

class _YcHomeBodyState extends State<YcHomeBody> {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const SecondPage()),
          );
        },
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
              'https://scpic3.chinaz.net/files/default/imgs/2023-06-07/f84b7dd1b1e82805_s_w285.jpg'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
    
    
  const SecondPage({
    
    Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.pop(context);
        },
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
              'https://scpic3.chinaz.net/files/default/imgs/2023-06-07/f84b7dd1b1e82805_s_w285.jpg'),
        ),
      ),
    );
  }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41897680/article/details/131070466