Flutter: animation

foreword

Learning reference: Old Meng flutter animation
Basically, the components used in development have their animations, and knowledge about animations is rarely used in general. So here only learn the basics about animation.

AnimationController

In Flutter AnimationControlleris a class for controlling animations. It can control the start, stop, reverse, reset and other operations of the animation, and can set the duration, curve and other properties of the animation.

AnimationController Usually initialized in initStatethe method and disposerelease the animation in

To use, AnimationControlleryou need to create an instance first, then set the animation duration, curve and other properties, and finally forward()start the animation by calling the method. During the animation running, you can reverse()reverse the animation by calling the method, stop()stop the animation by calling the method, and reset()reset the animation by calling the method.

AnimationControllerYou can also add a listener to monitor the state changes of the animation. addListener()For example, you can update the UI interface by adding a method to monitor the value change of the animation. addStatusListenerMonitor the state of the animation by adding

// 单个 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("点击变大"),
      ),
    ));
  }
}

insert image description here

Tween

In Flutter Tweenis a class used to define the interpolation calculation between the start value and the end value in the animation. It can map values ​​in one range to values ​​in another range, allowing for animation

The above case can be modified to

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("点击变大"),
      ),
    ));
  }
}

You can also change the color in the same way

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("点击改变颜色"),
      ),
    ));
  }
}

insert image description here

Curve

Another important concept in animation is Curve, which is the animation execution curve. The effect of the animation can be changed at various rates such as constant speed, acceleration, deceleration, parabola, etc.

// 使用了 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;
    });
  });

animation component

The Flutter system provides more than 20 animation components, all of which are implemented based on the core knowledge of animation. Animation components fall into two categories:

  • Implicit animation components: just provide the start and end values ​​of the animation to the component, the component creates AnimationController, Curve, Tween, executes the animation, and releases the AnimationController
  • Explicit animation components: You need to set up AnimationController to control the execution of animation. Using explicit animation can complete any implicit animation effect, and even have more functions, but you need to manage the AnimationController life cycle of the animation
  • There is a universal component in the display animation component and the implicit animation component. They are AnimatedBuilder and TweenAnimationBuilder. When the animation component we want does not exist in the system, these two components can be used

Use of implicit animation components

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("点击改变颜色"),
              ),
            )));
  }
}

insert image description here
**TweenAnimationBuilder**
TweenAnimationBuilderis an animation builder in Flutter that can be used to create an animation component that animates between two values. Using TweenAnimationBuilderneeds to specify an interpolator ( Tween) between two values, as well as the duration of the animation and a callback function when the animation ends.

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("点击改变颜色"),
                  );
                })));
  }
}

insert image description here

list animation

AnimatedListProvides a simple way to add transition animation when the list data changes

The main properties of AnimatedList are as follows.

Attributes illustrate
itemBuilder A function, each index of the list will be called, this function has an animation parameter, which can be set to any animation
initialItemCount the number of items
scrollDirection Scroll direction, default vertical
controller scroll controller

The insertion and deletion of list data has the animation of entering and exiting the field, and the AnimatedListStatespecified method needs to be called. Only deleting the original data and calling setStatethe method has no animation effect

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]),
                      ),
                    ),
                  );
                }))
      ],
    );
  }
}

This thing is quite difficult to do, and many problems have arisen. One thing to keep in mind when deleting:

  • Attention should be paid to the deletion operation, the data in the list must be deleted first and the state of AnimatedListState should be deleted
  • And about the index subscript operation, it should be placed before the delete operation, otherwise it will cause the subscript error to be reported when deleting

The explanation I found about the simplicity of adding and the complexity of deletion is:

In AnimatedList, the new operation only needs to add data to the list and call the insertItem method of AnimatedListState, and AnimatedListState will automatically handle the animation effect.
But the deletion operation needs to manually handle the animation effect, because AnimatedListState cannot automatically handle the deletion animation. Therefore, you need to manually call the removeItem method of AnimatedListState, and specify the implementation of the deletion animation.

insert image description here

Hero

HeroUsed to achieve a smooth transition effect between two pages. It can transform a widget from one page to another while keeping its appearance and position unchanged
.
HeroAnimations are often used when transferring images or other media content between two pages, giving the user the impression that the content moves smoothly between the two pages.

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'),
        ),
      ),
    );
  }
}

insert image description here

Guess you like

Origin blog.csdn.net/weixin_41897680/article/details/131070466