Flutter - animation usage and custom animation components (tabbar jump animation or text shake)

demo address: https://github.com/iotjin/jh_flutter_demo
代码不定时更新,请前往github查看最新代码

sequence

When the tabbar on the main page of the APP is clicked, some tabbar icons zoom or jump animation, and there is a shaking effect when the text is clicked.
In this article, some zoom or displacement animations are realized through flutter. Packaged into several custom components.
These custom animation components can implement subcomponents to call animations through properties or methods. Animation
effects: shake, shake left and right, zoom in first and then zoom out, etc., also support customization

A three-party library with similar effects:
animate_do: ^3.0.2

Article:
Introduction to flutter animation

renderings

Please add a picture description

Please add a picture description

Introduction to animation

Animation refers to the continuous playback of a series of static images to form a visual dynamic effect. In Flutter, animation achieves dynamic effects by interpolating attributes and gradually changing attribute values.

In Flutter, animation is achieved through a series of animation objects and controllers. An animation object is usually a value interpolator that maps a value from one range to another. Flutter provides various types of animation objects, such as Tween, Curve, etc.
The animation controller is used to control the state and progress of the animation. Controllers can start, stop, and reverse animations, and can listen for state changes and progress changes of animations. Flutter provides the AnimationController class to implement animation controllers.

animation type

In Flutter, animations can be divided into two types: explicit animations and implicit animations.

  • Explicit animation: This kind of animation is defined by the developer himself, and is usually AnimationControllerimplemented using the animation classes provided by Flutter. Developers can control the start, end, pause and resume of the animation by setting the properties of the controller. For example, classes such as Animation, Tween, and Curve can be used to create various types of animation effects.

  • Implicit animation: This kind of animation is automatically handled by the framework and implemented through the AnimatedWidget class, which automatically updates the UI interface according to the change of the animation. For example, when a Widget in an app changes, Flutter automatically creates an animation to smoothly transition to the new Widget state.

Some concepts of Flutter animation

  • The Flutter animation framework is Animationclass-based.
  • AnimationA class is an abstract class that defines the basic properties and methods for animation.
  • Flutter provides many Animationsubclasses, including Tween, Curve, Intervaland AnimationControlleretc.
  • TweenThe class is used to define the start value and end value of the animation, Curvethe class is used to define the acceleration and deceleration curve of the animation, Intervalthe class is used to define the time interval of the animation, and AnimationControllerthe class is used to define the controller of the animation.
  • AnimationController: Animation controller, used to control the start, end, pause, resume and other operations of animation. You can set animation duration, speed curve and other properties.
  • Tween: An interpolator used to calculate changes in property values. You can set the start value and end value of the property, as well as the type of interpolation (such as linear interpolation, curve interpolation, etc.). It can be a number, a color, a rectangle or any other type of value.
  • Animation: animation object, used to save the change of property value. Represents the start and end values ​​of the animation. It can be a number, a color, a rectangle or any other type of value. You can monitor animation changes through the addListener() method, and update the UI in the callback function.
  • Listener: Monitor the state changes of the animation, such as animation start, end, stop, etc.
  • AnimatedBuilder: Animation builder, used to build animation components. You can pass the Animation object to the child component, so that the child component changes with the animation.
  • Curve: Indicates the time curve of the animation, used to control the change speed of the animation. You can set the curve type (such as constant speed, acceleration, deceleration, first acceleration and then deceleration, etc.).
  • Hero animation: used to achieve a smooth transition effect of the same element in two pages when routing jumps. You can wrap the same element in two pages in the Hero component and set a unique tag value.
  • AnimatedSwitcher: Used to achieve smooth switching effects between multiple components. Multiple components can be wrapped in the AnimatedSwitcher component, and different key values ​​can be set when the components are switched.

Flutter's AnimationStatusenumeration type defines four states of animation (begin: 0.0, end: 1.0):

    1. dismissed: The animation has stopped and the value has returned to its initial state. That is, the animation has a value of 0.0.
    1. forward: The animation is playing forward. That is, the value of the animation gradually increases from 0.0 to 1.0. (forward run)
    1. reverse: The animation is playing in reverse. That is, the value of the animation gradually decreases from 1.0 to 0.0. (reverse run)
    1. completed: The animation has stopped and the value has reached its final state. That is, the animation has a value of 1.0.

Animation control method:

  • forward: forward animation.
  • reverse: Perform animation in reverse.
  • repeat: The animation is performed repeatedly.
  • reset: Reset the animation.

The common Curve types in Flutter are as follows:

A Curve type animation website
https://cubic-bezier.com/

Curves.linear: Linear animation curve, that is, uniform motion, suitable for scenes that need to move at a constant speed.
Curves.ease: The default easing animation curve, suitable for most scenes.
Curves.easeIn: Quickly enter the animation curve, suitable for scenes that need to be quickly entered, such as button clicks, etc.
Curves.easeOut: Quickly exit the animation curve, suitable for scenes that need to exit quickly, such as closing the pop-up window, etc.
Curves.easeInOut: Quickly enter and quickly exit the animation curve, suitable for scenes that need to quickly enter and quickly exit, such as page switching, etc.
Curves.fastLinearToSlowEaseIn: Fast and uniform movement and then slow entry to the animation curve, suitable for scenes that require fast and constant movement and then slow entry, such as list scrolling, etc.
Curves.bounceIn: Elastic entry animation curve, suitable for scenes that require elastic entry, such as the appearance of pop-up windows.
Curves.bounceOut: Elastic exit animation curve, suitable for scenarios that require elastic exit, such as pop-up window closing, etc.
Curves.elasticIn: Elastic entry animation curve, suitable for scenes that require elastic entry, such as pull-to-refresh.
Curves.elasticOut: Elastic exit animation curve, suitable for scenarios that require elastic exit, such as sliding the list to the bottom, etc.
Different Curve types are suitable for different scenarios. Choosing the appropriate Curve type according to specific needs can make the animation effect more natural and smooth.
For example, if you need to achieve a fast entry button click effect, you can choose Curves.easeIn;
if you need to achieve a slow list scrolling effect, you can choose Curves.fastLinearToSlowEaseIn;
If you need to achieve an elastic pop-up window effect, you can choose Curves.bounceIn and so on.

Common animation implementation

Flutter provides a variety of ways to implement animations, including some implicit animations (such as Opacity, AnimatedContainer, AnimatedPositioned), Tween animations, Curve animations, combined animations, etc.

implicit animation

You can use Flutter's implicit animations to achieve smooth transitions. Here are some common implicit animation examples:

  1. Opacity animation: Fade in and out by changing the transparency.
Opacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: Duration(milliseconds: 500),
  child: Container(
    // your widget
  ),
)
  1. Scale animation: realize the zoom effect by changing the size.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  width: _expanded ? 200.0 : 100.0,
  height: _expanded ? 200.0 : 100.0,
  child: Container(
    // your widget
  ),
)
  1. Rotation animation: realize the rotation effect by changing the angle.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  transform: Matrix4.rotationZ(_expanded ? pi / 4 : 0),
  child: Container(
    // your widget
  ),
)
  1. Panning animation: achieve panning effect by changing the position.
AnimatedPositioned(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  left: _expanded ? 100.0 : 0.0,
  top: _expanded ? 100.0 : 0.0,
  child: Container(
    // your widget
  ),
)
  1. Color animation: achieve color transition effects by changing colors.
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
  color: _expanded ? Colors.red : Colors.blue,
  child: Container(
    // your widget
  ),
)

Tween animation

Tween animation is one of the most basic animation types in Flutter. It is used to interpolate between two values, creating a smooth transition. For example, we can use a Tween animation to create a gradient between two colors. The following sample code demonstrates how to use Tween animation to create a color gradient effect:

class ColorTweenAnimation extends StatefulWidget {
    
    
  
  _ColorTweenAnimationState createState() => _ColorTweenAnimationState();
}

class _ColorTweenAnimationState extends State<ColorTweenAnimation>
    with SingleTickerProviderStateMixin {
    
    
  AnimationController _controller;
  Animation<Color> _animation;

  
  void initState() {
    
    
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);
    _animation = ColorTween(
      begin: Colors.red,
      end: Colors.blue,
    ).animate(_controller);
  }

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

  
  Widget build(BuildContext context) {
    
    
    return Container(
      color: _animation.value,
      height: 200,
      width: 200,
    );
  }
}

Curve animation

Curve animation is used to control the time curve of animation. Flutter provides many predefined Curve curves, such as linear curves, acceleration curves, deceleration curves, etc. The following sample code demonstrates how to use Curve animation to create a bouncing effect:

class BounceAnimation extends StatefulWidget {
    
    
  
  _BounceAnimationState createState() => _BounceAnimationState();
}

class _BounceAnimationState extends State<BounceAnimation>
    with SingleTickerProviderStateMixin {
    
    
  AnimationController _controller;
  Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    )..repeat(reverse: true);
    _animation = Tween<double>(
      begin: 0,
      end: 100,
    ).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.bounceOut,
      ),
    );
  }

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

  
  Widget build(BuildContext context) {
    
    
    return Container(
      alignment: Alignment.bottomCenter,
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
    
    
          return Container(
            height: _animation.value,
            width: 50,
            color: Colors.red,
          );
        },
      ),
    );
  }
}

Hero animation

Hero animation is a very popular animation type in Flutter. It is used to smoothly transition shared elements between different screens. For example, we can create a Hero animation between two different screens so that shared elements transition smoothly to the new screen state. The following sample code demonstrates how to use Hero animation to create a shared element transition effect:

class HeroAnimation extends StatelessWidget {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => HeroDetailScreen()),
          );
        },
        child: Hero(
          tag: 'image',
          child: Image.network(
            'https://picsum.photos/250?image=9',
            height: 300,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

class HeroDetailScreen extends StatelessWidget {
    
    
  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      body: GestureDetector(
        onTap: () {
    
    
          Navigator.pop(context);
        },
        child: Hero(
          tag: 'image',
          child: Image.network(
            'https://picsum.photos/250?image=9',
            height: double.infinity,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

combined animation

Combined animation can combine multiple animations together to make the animation effect richer.

AnimationController _controller;
Animation<double> _animation1;
Animation<double> _animation2;


void initState() {
    
    
  super.initState();
  _controller = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  );
  _animation1 = Tween<double>(
    begin: 0,
    end: 1,
  ).animate(_controller);
  _animation2 = Tween<double>(
    begin: 0,
    end: 1,
  ).animate(CurvedAnimation(
    parent: _controller,
    curve: Interval(0.5, 1),
  ));
}


Widget build(BuildContext context) {
    
    
  return Scaffold(
    body: Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (BuildContext context, Widget child) {
    
    
          return Transform.scale(
            scale: _animation1.value + _animation2.value,
            child: child,
          );
        },
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
    
    
        _controller.forward();
      },
      child: Icon(Icons.play_arrow),
    ),
  );
}

Custom animation components:

Jumping animation, the default is to zoom in first, then zoom out and then restore

///  jh_pulse_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 跳动动画,先放大再缩小再还原
import 'package:flutter/material.dart';

class JhPulseAnimationView extends StatefulWidget {
    
    
  const JhPulseAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 300),
    this.begin = 1.1,
    this.end = 0.9,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhPulseAnimationView> createState() => _JhPulseAnimationViewState();
}

class _JhPulseAnimationViewState extends State<JhPulseAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 1, end: widget.begin), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.begin, end: widget.end), weight: 1),
      TweenSequenceItem<double>(tween: Tween(begin: widget.end, end: 1), weight: 1),
    ]).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeIn));
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reset();
        widget.onCompleted?.call();
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget? child) {
    
    
          return Transform.scale(
            scale: _animation.value,
            child: widget.child,
          );
        },
      ),
    );
  }

  
  void didUpdateWidget(covariant JhPulseAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Zoom animation, zoom in and restore by default

///  jh_scale_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 缩放动画,默认放大再还原
import 'package:flutter/material.dart';

class JhScaleAnimationView extends StatefulWidget {
    
    
  const JhScaleAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.begin = 1.0,
    this.end = 1.1,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhScaleAnimationView> createState() => _JhScaleAnimationViewState();
}

class _JhScaleAnimationViewState extends State<JhScaleAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = Tween<double>(begin: widget.begin, end: widget.end).animate(_animationController)
      ..addListener(() {
    
    
        setState(() {
    
    });
      })
      ..addStatusListener((status) {
    
    
        if (status == AnimationStatus.completed) {
    
    
          _animationController.reverse();
        } else if (status == AnimationStatus.dismissed) {
    
    
          widget.onCompleted?.call();
        }
      });
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward();
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: Transform.scale(
        scale: _animation.value,
        child: widget.child,
      ),
    );
  }

  
  void didUpdateWidget(covariant JhScaleAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Scale animation 2, the default zoom out and then restore

///  jh_scale_animation_view2.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 缩放动画,默认缩小再还原
import 'package:flutter/material.dart';

class JhScaleAnimationView2 extends StatefulWidget {
    
    
  const JhScaleAnimationView2({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.begin = 1.0,
    this.end = 0.9,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhScaleAnimationView2> createState() => _JhScaleAnimationView2State();
}

class _JhScaleAnimationView2State extends State<JhScaleAnimationView2> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = Tween<double>(begin: widget.begin, end: widget.end).animate(_animationController);
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reverse().then((value) {
    
    
          widget.onCompleted?.call();
        });
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: ScaleTransition(
        scale: _animation,
        child: widget.child,
      ),
    );
  }

  
  void didUpdateWidget(covariant JhScaleAnimationView2 oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Shake (displacement) animation, support up and down/left and right shaking, default left and right shaking

///  jh_shake_animation_view.dart
///
///  Created by iotjin on 2023/03/25.
///  description: 抖动(位移)动画,支持上下/左右抖动,默认左右抖动
import 'package:flutter/material.dart';

enum ShakeDirection {
    
    
  horizontal,
  vertical,
}

class JhShakeAnimationView extends StatefulWidget {
    
    
  const JhShakeAnimationView({
    
    
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 100),
    this.direction = ShakeDirection.horizontal,
    this.begin = -5,
    this.end = 5,
    this.isAnimating = false,
    this.onCompleted,
  }) : super(key: key);

  final Widget child; // 点击child自身触发动画(方式一)
  final Duration duration;
  final ShakeDirection direction;
  final double begin;
  final double end;
  final bool isAnimating; // 为true触发动画(方式二)
  final Function? onCompleted;

  
  State<JhShakeAnimationView> createState() => _JhShakeAnimationViewState();
}

class _JhShakeAnimationViewState extends State<JhShakeAnimationView> with SingleTickerProviderStateMixin {
    
    
  late AnimationController _animationController;
  late Animation<double> _animation;

  
  void initState() {
    
    
    super.initState();

    _init();
  }

  
  void dispose() {
    
    
    _animationController.dispose();

    super.dispose();
  }

  _init() {
    
    
    _animationController = AnimationController(vsync: this, duration: widget.duration);
    _animation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0, end: widget.begin), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.begin, end: widget.end), weight: 1),
      TweenSequenceItem(tween: Tween(begin: widget.end, end: 0), weight: 1),
    ]).animate(_animationController);
    _startAnimation();
  }

  _startAnimation([isClick = false]) {
    
    
    if (widget.isAnimating || isClick) {
    
    
      _animationController.forward().then((value) {
    
    
        _animationController.reset();
        widget.onCompleted?.call();
      });
    }
  }

  
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      onTap: () => _startAnimation(true),
      child: AnimatedBuilder(
        animation: _animationController,
        builder: (BuildContext context, Widget? child) {
    
    
          var x = widget.direction == ShakeDirection.horizontal ? _animation.value : 0.0;
          var y = widget.direction == ShakeDirection.vertical ? _animation.value : 0.0;
          return Transform.translate(
            offset: Offset(x, y),
            child: widget.child,
          );
        },
      ),
    );
  }

  
  void didUpdateWidget(covariant JhShakeAnimationView oldWidget) {
    
    
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);

    if (widget.isAnimating != oldWidget.isAnimating) {
    
    
      _startAnimation();
    }
  }
}

Guess you like

Origin blog.csdn.net/iotjin/article/details/130016992