In Android, we can design our own Latyout animation based on layout changes, and even vector animation can seamlessly switch the path, such as the natural transition from circle to rectangle
The name of the Flutter has been changed to “implicit animation”. I would like to say that there is no need to make coder feel uncomfortable. It is not good to continue the android tradition
AnimatedSwitcher
– The widget can play its own animation when the content changesAnimatedContainer
– The Container is used like a Container. When the color, width, height, and rounded corners of the Container change, the animation can not be controlled. It is similar to the animation of pathAnimatedCrossFade
– Animation can be displayed when switching between different layouts, but you can’t set your own animation, the default is to fade in and out, and the display is not good when switching between different sizesDecoratedBoxTransition
– Border animation, the core is to change the shape by changing the rounded Angle, this change is natural transition, this is the same as path animationAnimatedDefaultTextStyle
– The switching animation when the text style is changed, mainly presents the animation about the size transformation, the color gradient is not obvious, but the experience is not good, the change of font thickness when the size word is changed is really a little bit irritating, a little bit slowAnimatedModalBarrier
– The color change animation must be placed in the child of the widget. It has a specific application scenario, such as changing the background color when the dialog pops upAnimatedOpacity
– Opacity change animationAnimatedPhysicalModel
– Shadow transform animation, the setup is a bit complicatedAnimatedPositioned
– Stack widget position and size change animationAnimatedSize
– Widget size change animation
AnimatedContainer
AnimatedContainer, as the name implies, is the Container that drives the drawing. The property Settings are the same as those used in the Container. The difference is that you can set the animation time and interpolator
Animation effect is similar to vector animation, which can achieve seamless switching between the states before and after, and the system completes the numerical output of each frame of animation. But can achieve the effect of vector animation attributes only: color, width, height, rounded corners, other are not, such as picture switching is a switch, shape switching, such as zooming from a circle to a rectangle, the circle in the maximum moment will switch to a rectangle, rectangle then slowly zooming
The use of AnimatedContainer is to write the property value outside and change the property value with setState to switch the animation
Here’s my example:
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
double width = 50;
double height = 50;
Color color = Colors.blueAccent;
BoxShape shape = BoxShape.circle;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedContainer(
duration: Duration(seconds: 1),
width: width,
height: height,
decoration: BoxDecoration(
color: color,
shape: shape,
),
),
RaisedButton(
child: Text("AAA"),
onPressed: () {
setState(() {
width = 300;
height = 300; color = Colors.pink; shape = BoxShape.rectangle; }); },),,); }}Copy the code
A good example is given here, as far as the limits of the AnimatedContainer can be achieved, where the shape is changed by changing the Angle of the rounded rectangle: borderradius.circular (8);
class TestWidgetState extends State<TestWidget>
with SingleTickerProviderStateMixin {
double _width = 50;
double _height = 50;
Color _color = Colors.green;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: AnimatedContainer(
// Use the properties stored in the State class.
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
// Define how long the animation should take.
duration: Duration(seconds: 1),
// Provide an optional curve to make the animation feel smoother.
curve: Curves.fastOutSlowIn,
),
),
RaisedButton(
child: Icon(Icons.play_arrow),
// When the user taps the button
onPressed: () {
// Use setState to rebuild the widget with new values.
setState(() {
// Create a random number generator.
final random = Random();
// Generate a random width and height.
_width = random.nextInt(300).toDouble();
_height = random.nextInt(300).toDouble();
// Generate a random color.
_color = Color.fromRGBO(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
1,);// Generate a random border radius.
_borderRadius =
BorderRadius.circular(random.nextInt(100).toDouble()); }); },),,); }}Copy the code
Finally, note that the AnimatedContainer animation can only manipulate the properties of the Container itself, not the child widgets in the Child
AnimatedSwitcher
The basic use
AnimatedSwitcher is an animation style provided by Flutter for switching between widgets. Currently, content changes can only be supported for the same widget. Switching between widgets of different types is still being investigated
There are several AnimatedSwitcher attributes:
child
– Content switch animation on widgetsduration
– Animation from A -> B timereverseDuration
– Animation is reversed from B -> A timeswitchInCurve
– Animation from A -> B animation interpolator,Curves.linear
switchOutCurve
– Get interpolator in reversetransitionBuilder
Animation –layoutBuilder
– The component that wraps the old and new widgets, a Stack by default
Note that the flutter widget tree has its own cache. The same widget will not be rebuilt if its content is updated, but the AnimatedSwitcher must have 2 widgets to animate it. Therefore, manually set the key to circumvent the widget caching mechanism in Flutter
This part of the web is mostly from official documentation: 9.6 general “AnimatedSwitcher”
The official document does not have GIF, can not see the effect, here I post the effect and code:
class TestWidgetState extends State<TestWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
// Perform the zoom animation
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$_count'.// Display the specified key. Different keys are treated as different Text so that animation can be performed
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
RaisedButton(
child: const Text('+ 1',),
onPressed: () {
setState(() {
_count += 1; }); },),],),); }}Copy the code
Then let’s do another example of an icon toggle
class TestWidgetState extends State<TestWidget> {
IconData _actionIcon = Icons.delete;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
transitionBuilder: (child, anim) {
return ScaleTransition(child: child, scale: anim);
},
duration: Duration(milliseconds: 200),
child: IconButton(
key: ValueKey(_actionIcon),
icon: Icon(_actionIcon),
),
),
RaisedButton(
child: Text("Toggle icon"),
onPressed: () {
setState(() {
if (_actionIcon == Icons.delete)
_actionIcon = Icons.done;
else_actionIcon = Icons.delete; }); },),],),); }}Copy the code
AnimatedSwitcher principle
Actually the principle is very simple, say to understand. Because the Child Widget in the AnimatedSwitcher has a different key, each time the content of the Child Widget changes, it is considered that a new widget appears and will be rebuilt. We get the new old widget in the didUpdateWidget, animate the old widget backward, animate the new widget forward, and that’s it. Here’s the source code:
void didUpdateWidget(AnimatedSwitcher oldWidget) {
super.didUpdateWidget(oldWidget);
// Check whether the new and old child have changed (return true if the key and type are equal)
if (Widget.canUpdate(widget.child, oldWidget.child)) {
// Child does not change...
} else {
// The child is changed, and a Stack is constructed to animate the old and new child separately
_widget= Stack(
alignment: Alignment.center,
children:[
// Old child application FadeTransition
FadeTransition(
opacity: _controllerOldAnimation,
child : oldWidget.child,
),
// New child application FadeTransition
FadeTransition(
opacity: _controllerNewAnimation,
child : widget.child,
),
]
);
// Perform a reverse exit animation for the old child
_controllerOldAnimation.reverse();
// Animate the forward entry for the new child_controllerNewAnimation.forward(); }}Copy the code
AnimatedSwitcher Advanced use
The AnimatedSwitcher will perform a forward and reverse animation between the old and new widgets. The special token is where the animation came from, so the old text comes in from the right, so the old text goes out from the right. In general, the animation must be executed sequentially
So we can do what we want, like in on the right, out on the left. We can do that, we don’t have to change the AnimatedSwitcher, we can change the animation API FlideTransition, all the animations are written on top of the AnimationWidget
For this, we’ll do what FlideTransition does internally, adding a minus sign to the X-axis when it flips, and that’s how we do it most of the time
This example is from the official document. I have changed the code to some extent, mainly to make it more convenient to use and higher encapsulation, and to make encapsulation for the X/Y axis
- This is a modified version of SlideTransition, internally using the original implementation of SlideTransition. The core is to process the Offset coordinate data in response to the inversion of the animation. Remember this routine, as well as everything else
enum FreeSlideTransitionMode {
reverseX,
reverseY,
}
class FreeSlideTransition extends AnimatedWidget {
Animation<Offset> animation;
var child;
Offset begin;
Offset end;
FreeSlideTransitionMode type;
// Different data processing for x and y axis reversal playback, with map bearing
Map<FreeSlideTransitionMode, Function(Animation animation, Offset offset)> worksMap = {
// X-axis reverse operation, typical application, right in and left out
FreeSlideTransitionMode.reverseX: (Animation animation, Offset offset) {
if (animation.status == AnimationStatus.reverse) {
return offset = Offset(-offset.dx, offset.dy);
}
return offset;
},
FreeSlideTransitionMode.reverseY: (Animation animation, Offset offset) {
if (animation.status == AnimationStatus.reverse) {
return offset = Offset(offset.dx, -offset.dy);
}
returnoffset; }};// Constructor polymorphisms are a bit cumbersome to write and look like
FreeSlideTransition(this.type, this.child,
{Key key, Animation<double> animation, Offset begin, Offset end})
: super(key: key, listenable: animation) {
this.animation = Tween<Offset>(begin: begin, end: end).animate(animation);
}
FreeSlideTransition.reverseX(
{Widget child,
Animation<double> animation,
Key key,
Offset begin,
Offset end})
: this(FreeSlideTransitionMode.reverseX, child,
key: key, animation: animation, begin: begin, end: end);
FreeSlideTransition.reverseY(
{Widget child,
Animation<double> animation,
Key key,
Offset begin,
Offset end})
: this(FreeSlideTransitionMode.reverseY, child,
key: key, animation: animation, begin: begin, end: end);
@override
Widget build(BuildContext context) {
var offset = animation.value;
offset = worksMap[type](animation, offset);
// SlideTranslation is implemented in this way
returnFractionalTranslation( translation: offset, child: child, ); }}Copy the code
- Try it out: In Column, sometimes the position will be invalid, and a padding in the middle will be fine
class Test3 extends State<TestWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
// Perform the zoom animation
return FreeSlideTransition.reverseY(
animation: animation,
begin: Offset(0.1),
end: Offset(0.0),
child: child,
);
},
child: Text(
'$_count'.// Display the specified key. Different keys are treated as different Text so that animation can be performed
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
// Padding(
/ / padding: EdgeInsets. All (20.0),
/ /),
// Text("AAA"),
RaisedButton(
child: Text(
'+ 1',
),
onPressed: () {
setState(() {
_count += 1; }); },),],),); }}Copy the code
This FreeSlideTransition can be stored in the lib library. If you want to write this transition, you can write it in the lib library
Some of you don’t understand why you have to write tween in AnimatedSwitcher. The default value of the AnimationControl is [0-1]. If we want to use our own data, we have to write our own Tween
AnimatedCrossFade
AnimatedCrossFade displays animations when switching between different layouts, but you can’t set the animations yourself. The default is to fade in and out, and it doesn’t look good when switching between different sizes
I’m going to do a GIF to show you how the widgets feel when they switch between different sizes. Small to large is fine, but small to small is not
AnimatedCrossFade: Unfortunately, you can’t set the animation by yourself. The default is a gradient animation, which is quite limited
In code, we need to specify whether to display the Frist widget or the second widget. We write a flag around the widget. SetState changes this flag to trigger the animation
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isFristShow = true;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedCrossFade(
firstChild: Container(
alignment: Alignment.center,
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blueAccent,
),
child: Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
),
secondChild: Container(
alignment: Alignment.center,
width: 300,
height: 300,
child: Text("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
decoration:
BoxDecoration(shape: BoxShape.rectangle, color: Colors.pinkAccent),
),
crossFadeState: isFristShow
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: Duration(milliseconds: 300),
reverseDuration: Duration(milliseconds: 300),
),
RaisedButton(
child: Text("AAA"), onPressed: () { setState(() { isFristShow = ! isFristShow; }); },),,); }}Copy the code
DecoratedBoxTransition
DecoratedBoxTransition is a bounding animation, you can only animate a bounding animation, but it’s what we’ve been looking for, and it allows for a natural transition of widget shapes, just like we do with AnimatedContainer, The natural transition of the shape still depends on the degree of the rounded rectangle
DecoratedBoxTransition attributes:
child
–decoration
– Represents the change in the animation property value passed in, and fills the border of the child by fetching its valueposition
– Represents the position of the border animation, either the foreground position or the background position, which will cover the Child element
The DecoratedBoxTransition child does not have a background. The DecorationTween value generator automatically assigns the shape background to the child
_animation = DecorationTween(
begin: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
color: Colors.red,
),
end: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)),
color: Colors.green,
),
)
Copy the code
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
Animation<Decoration> _animation;
AnimationController _controller;
Animation _curve;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,); _curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn); _animation = DecorationTween( begin: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(0.0)),
color: Colors.red,
),
end: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)), color: Colors.green, ), ).animate(_curve) .. addStatusListener((AnimationStatus state) {if (state == AnimationStatus.completed) {
_controller.reverse();
} else if(state == AnimationStatus.dismissed) { _controller.forward(); }}); _controller.forward(); }@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
children: <Widget>[
DecoratedBoxTransition(
position: DecorationPosition.background,
decoration: _animation,
child: Container(
child: Container(
padding: EdgeInsets.all(50),
child: Text("AAAAAA"() (() [(). }}Copy the code
AnimatedDefaultTextStyle
AnimatedDefaultTextStyle switching animated text style changes, mainly presents the transformation in terms of the size of the animation, color gradient is not obvious, but the place with bad experience is that the size of the word when switching font weight change real is a bit hot eye, especially when large size
The AnimatedDefaultTextStyle and AnimatedContainer are designed, used, and named in completely different ways. Google what are you up to, code quality review? AnimatedDefaultTextStyle and AnimatedContainer are actually the same thing, but there is no communication between the developers. It is a pain to make two things
AnimatedDefaultTextStyle is used as an outer widget for text. Instead of text, it should be AnimatedContainer. Most AnimatedDefaultTextStyle AnimatedDefaultTextStyle AnimatedDefaultTextStyle AnimatedDefaultTextStyle AnimatedDefaultTextStyle AnimatedDefaultTextStyle AnimatedDefaultTextStyle
The trigger of the animation is again controlled by the outer variable, which is triggered by staState
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var _isSelected = true;
var info1 = "Flutter !!!";
var info2 = "is not you !!!";
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
AnimatedDefaultTextStyle(
softWrap: false,
textAlign: TextAlign.right,
maxLines: 1,
overflow: TextOverflow.ellipsis,
curve: Curves.linear,
duration: Duration(milliseconds: 300),
child: Text( info2),
style: _isSelected
? TextStyle(
fontSize: 10.0,
color: Colors.red,
fontWeight: FontWeight.bold,
)
: TextStyle(
fontSize: 30.0,
color: Colors.black,
fontWeight: FontWeight.w300,
),
),
RaisedButton(
child: Text("AA"), onPressed: () { setState(() { _isSelected = ! _isSelected; }); },),,); }}Copy the code
If the number of lines before and after the text switch is not the same, the animation will be more ugly, you see the following example, when using you remember to change one line to multiple lines
AnimatedModalBarrier
AnimatedModalBarrier
The color change animation is unique in that it must be placed in the child of the widget. It has specific application scenarios, such as changing the background color when the dialog pops up
AnimatedModalBarrier takes several parameters, which I don’t know what they are other than color:
color
– Color value animation changesdismissible
– Whether to touch the current ModalBarrier to pop up the current routesemanticsLabel
– Semantic labelsbarrierSemanticsDismissible
– Whether the ModalBarrier semantics are included in the semantic tree
Color receives an animation object, so we can set the color values, duration, add controls, etc. The API has much more freedom than other similar transform animation apis
In the example below I set up a loop
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _curve;
Animation<Color> animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,).. addStatusListener((AnimationStatus state) {if (state == AnimationStatus.completed) {
_controller.reverse();
} else if(state == AnimationStatus.dismissed) { _controller.forward(); }}); animation = ColorTween( begin: Colors.blue, end: Colors.pinkAccent, ).animate(_controller); }@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
Container(
width: 300,
height: 300,
child: AnimatedModalBarrier(
semanticsLabel: "StackBarrier",
barrierSemanticsDismissible: true,
dismissible: true,
color: animation,
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"), onPressed: () { _controller.forward(); },),),],); }}Copy the code
AnimatedOpacity
AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity: AnimatedOpacity
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Color> animation;
double opacity = 1.0;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,); animation = ColorTween( begin: Colors.blue, end: Colors.pinkAccent, ).animate(_controller); }@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
AnimatedOpacity(
curve: Curves.fastOutSlowIn,
opacity: opacity,
duration: Duration(seconds: 1),
child: Container(
width: 300,
height: 300,
child: AnimatedModalBarrier(
semanticsLabel: "StackBarrier",
barrierSemanticsDismissible: true,
dismissible: true,
color: animation,
),
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
opacity = 0.3; _controller.forward(); }); },),),],); }}Copy the code
AnimatedPhysicalModel
The AnimatedPhysicalModel properties are as follows:
shape
: Shadow shapeclipBehavior
: Shadow cropping methodClip.none
No pattern:Clip.hardEdge
: Slightly faster clipping speed, but easy to distort, serratedClip.antiAlias
: Cutting edge anti-aliasing, making cutting smoother, this mode cutting speed is faster than antiAliasWithSaveLayer, but slower than hardEdgeClip.antiAliasWithSaveLayer
: After clipping, it has anti-aliasing features and allocates a screen buffer. All subsequent operations are carried out in the buffer
borderRadius
: Border of the backgroundelevation
: Depth of the shadow color valuecolor
Background color:animateColor
: Whether the background color is displayed in animated formshadowColor
: Animation value of the shadowanimateShadowColor
: Whether the shadows are animated
Look at a lot, but we don’t meng, in fact, a useful, is shadowColor, we change the shadowColor will trigger animation, but color this attribute must be set, or error
In terms of overall animation, I really don’t know where this animation is applied, whether it is like the above one, to do the change of scenery on the back of the click, who knows…
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isShadow = true;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.loose,
children: <Widget>[
AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
color: Colors.grey.withOpacity(0.2),
clipBehavior: Clip.antiAliasWithSaveLayer,
borderRadius: BorderRadius.circular(12.0),
animateColor: true,
animateShadowColor: true,
shape: BoxShape.rectangle,
shadowColor: isShadow ? _shadowColor1 : _shadowColor2,
elevation: 5.0,
duration: Duration(milliseconds: 300),
child: Container(
width: 200,
height: 200,
child: Text("AA"),
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"), onPressed: () { setState(() { isShadow = ! isShadow; }); },),),],); }}Copy the code
AnimatedPositioned
And you can see by the name of the animatedtoy, which is exactly the same AS the ORIGINAL API, that would be nice, so you would know exactly what it is, but again diss the name and the design of the other piece of rubbish
I don’t want to talk about that, because you can read the code
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isPosition = true;
var top1 = 20.0;
var left1 = 20.0;
var width1 = 200.0;
var height1 = 200.0;
var top2 = 100.0;
var left2 = 100.0;
var width2 = 300.0;
var height2 = 300.0;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
AnimatedPositioned(
top: isPosition ? top1 : top2,
left: isPosition ? left1 : left2,
width: isPosition ? width1 : width2,
height: isPosition ? height1 : height2,
child: Container(
color: Colors.blueAccent,
),
duration: Duration(milliseconds: 300),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"), onPressed: () { setState(() { isPosition = ! isPosition; }); },),),],); }}Copy the code
AnimatedSize
In the GIF below, you can see that if the size of the widget is changed, the widget will be moved, but the size will not be animated. This is not very nice
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
double size1 = 200;
double size2 = 300;
var isSize = true;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text("AA"), onPressed: () { setState(() { isSize = ! isSize; }); }, ), AnimatedSize( alignment: Alignment.center, curve: Curves.fastOutSlowIn, vsync:this,
duration: Duration(seconds: 1),
reverseDuration: Duration(seconds: 2), child: Container( width: isSize ? size1 : size2, height: isSize ? size1 : size2, color: Colors.blueAccent, ), ), ], ); }}Copy the code