Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize Flutter Animation

2025-04-13 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly shows you "how to achieve Flutter animation", the content is easy to understand, clear, hope to help you solve your doubts, the following let the editor lead you to study and learn "how to achieve Flutter animation" this article.

The three cores of animation

In order to achieve animation, the following three elements must be provided:

Ticker

Animation

AnimationController

Here is a brief introduction to these elements, which will be explained in more detail later.

Ticker

Simply put, the Ticker class sends a signal in a regular time range (about 60 times per second), thinking of it as your watch ticking every second.

When Ticker starts, each incoming tick calls back the callback method of Ticker since the arrival of the first tick.

Important hint

Although all ticker may be started at different times, they are always executed synchronously, which is useful for some synchronous animations.

Animation

Animation is really nothing special, just a value that can change with the life cycle of the animation (there is a specific type), and the way the value changes with the animation time can be linear (for example, 1, 2, 3, 4, 5...), or more complex (see the "Curves curve" below).

AnimationController

AnimationController is a controller that can control one or more animations (start, end, repeat). In other words, it allows the Animation value mentioned above to change from a minimum to a maximum according to a speed within a specified period of time.

Introduction to the AnimationController class

This class controls the animation. To be more accurate, I would rather say "control a scene", because as we will see later, several different animations can be controlled by the same controller.

So, using this AnimationController class, we can:

Start a sub-animation and play it forward or backward

Stop a sub-animation

Set a specific value for the subanimation

Define the boundaries of animation valu

The following pseudo-code shows the different initialization parameters in this class

AnimationController controller = new AnimationController (value: / / the current value of the animation, usually 0.0 (= default) lowerBound: / / the lowest value of the animation, usually 0.0 (= default) upperBound: / / the highest value of the animation, usually 1.0 (= default) duration: / / the total duration of the whole animation (scene) vsync: / / the ticker provider debugLabel: / / a label to be used to identify the controller / / during debug session) Copy the code

In most cases, value,lowerBound,upperBound and debugLabel are not designed to initialize AnimationController.

How to bind AnimationController to Ticker

In order for the animation to work properly, you must bind AnimationController to Ticker.

Typically, you can generate a Ticker to bind to a StatefulWidget instance.

Class _ MyStateWidget extends State with SingleTickerProviderStateMixin {AnimationController _ controller; @ override void initState () {super.initState (); _ controller = new AnimationController (duration: const Duration (milliseconds: 1000), vsync: this,);} @ override void dispose () {_ controller.dispose (); super.dispose ();}.} copy the code

Line 2 tells Flutter that you want a single Ticker, and this Ticker is linked to the MyStateWidget instance.

8-10 lines

Initialization of the controller. The total duration of the scene (sub-animation) is set to 1000 milliseconds and is bound to Ticker (vsync:this).

The implicit parameters are: lowerBound = 0 and upperBound = 1. 0

16 lines

Very importantly, when the instance of the MyStateWidget page is destroyed, you need to release the controller.

TickerProviderStateMixin or SingleTickerProviderStateMixin?

If you have several Animation Controller cases and you want to have different Ticker, just replace SingleTickerProviderStateMixin with TickerProviderStateMixin.

Okay, I've bound the controller to Ticker, but does it work?

Because of ticker, there will be about 60 tick,AnimationController per second that will linearly produce values between the minimum and maximum values at a given time based on tick.

Examples of values generated in these 1000 milliseconds are as follows:

Image.png

We see the value change from 1000 (lowerBound) to 1.0 (upperBound) in 0 milliseconds. 51 different values were generated.

Let's extend the code to see how to use it.

Class _ MyStateWidget extends State with SingleTickerProviderStateMixin {AnimationController _ controller; @ override void initState () {super.initState (); _ controller = new AnimationController (duration: const Duration (milliseconds: 1000), vsync: this,); _ controller.addListener () {setState (() {});}); _ controller.forward () } @ override void dispose () {_ controller.dispose (); super.dispose ();} @ override Widget build (BuildContext context) {final int percent = (_ controller.value * 100.0) .round () Return new Scaffold (body: new Container (child: new Center (child: new Text ('$percent%'),);}} copy the code

Line 12 this line tells the controller that every time its value changes, we need to rebuild the Widget (through setState ())

Line 15

After Widget initialization is complete, we tell the controller to start counting (forward ()-> from lowerBound to upperBound)

26 lines

We retrieve the value of the controller (_ controller.value), and in this example, this value ranges from 0.0 to 1.0 (that is, 0 to 100%), and we get an integer expression of this percentage, which is displayed in the center of the page.

The concept of animation

As we can see, controller can return decimal values that are different from each other in a linear manner.

Sometimes we may have other needs such as:

Use other types of values, such as Offset,int...

The range of use is not from 0.0 to 1.0

Consider other types of changes other than linear changes to produce some effect

Use other value types

In order to be able to use other value types, the Animation class uses templates.

In other words, you can define:

Animation integerVariation;Animation decimalVariation;Animation offsetVariation; replication code uses a different range of values

Sometimes we want to use a different range instead of 0.0 and 1.0.

To define such a scope, we will use the Tween class.

To illustrate this, let's consider a situation where you want the angle to change from 0 to π / 2.

Animation angleAnimation = new Tween (begin: 0. 0, end: pi/2); copy code change type

As mentioned earlier, the default way to change the default value from lowerBound to upperBound is linear, and that's how controller controls it.

If you want the angle to vary linearly from 0 to π / 2 radians, bind Animation to AnimationController:

Animation angleAnimation = new Tween (begin: 0. 0, end: pi/2). Animate (_ controller); copy code

When you start the animation (through _ controller.forward ()), angleAnimation.value uses _ controller.value to get the value in the range [0.0; π / 2].

The following figure shows this linear change (π / 2 = 1.57)

Image.png

Predefined curve changes using Flutter

Flutter provides a predefined set of Curved changes, as follows:

Image.png

To use these curve effects:

Animation angleAnimation = new Tween (begin: 0. 0, end: pi/2). Animate (new CurvedAnimation (parent: _ controller, curve: Curves.ease, reverseCurve: Curves.easeOut)); copy code

This produces a value between the values [0; π / 2]:

When the animation is played in the forward direction, with values from 0 to π / 2, the Curves.ease effect is used.

When the animation is played backwards, the value is from π / 2 to 0, and the Curves.easeOut effect is used.

Control animation

This AnimationController class allows you to control animation through API. (the following are the most commonly used API):

_ controller.forward ({values of two intervals})

Require the controller to start generating values in lowerBound- > upperBound

The optional parameter of from can be used to force the controller to start "counting" from a value other than lowerBound

_ controller.reverse ({values of two intervals})

Require the controller to start generating values in upperBound- > lowerBound

The optional parameter of from can be used to force the controller to start "counting" from a value other than "upperBound"

_ controller.stop ({bool cancelled:true})

Stop running the animation

_ controller.reset ()

Reset the animation to start with LowerBound

_ controller.animateTo (double target, {Duration duration, Curve curve: Curves.linear})

Changes the current value of the animation to the target value.

_ controller.repeat ({double min,double max,Duration period})

Start running the animation in a forward direction, and restart the animation after the animation is complete. If min or max is defined, the number of times the animation can be repeated is limited.

For the sake of safety

Because animation may stop unexpectedly (for example, closing the screen), it is safer to add ".orCancel" when using one of the following API:

_ _ controller.forward () .orCancel; copy the code

This tip ensures that before _ controller is released, if Ticker is canceled, it will not cause an exception.

The concept of the scene

The word "scene" does not exist in the official document, but personally, I find it closer to reality. Let me explain.

As I said, one AnimationController manages one Animation. However, we may understand the word "animation" as a series of sub-animations that need to be played sequentially or overlapped. Put the sub-animation together, this is what I call the "scene".

Consider the following situation, where the entire duration of the animation is 10 seconds, and the effect we want to achieve is:

Within the first 2 seconds, a ball moves from the left side of the screen to the middle of the screen.

Then, it takes 3 seconds for the same ball to move from the center of the screen to the center of the top of the screen.

Finally, it takes five seconds for the ball to disappear. As you are most likely to have thought of, we must consider three different animations:

/ Definition of the _ controller with a whole duration of 10 seconds///AnimationController _ controller = new AnimationController (duration: const Duration (seconds: 10), vsync: this); / First animation that moves the ball from the left to the center///Animation moveLeftToCenter = new Tween (begin: new Offset (0.0, screenHeight / 2), end: new Offset (screenWidth / 2, screenHeight / 2)). Animate (_ controller) / Second animation that moves the ball from the center to the top///Animation moveCenterToTop = new Tween (begin: new Offset (screenWidth / 2, screenHeight / 2), end: new Offset (screenWidth / 2,0.0). Animate (_ controller); / Third animation that will be used to change the opacity of the ball to make it disappear///Animation disappear = new Tween (begin: 1.0, end: 0.0). Animate (_ controller); copy code

The question now is, how do we link (or choreograph) subanimations?

Interval

Composite animation can be achieved through the class Interval. But what is Interval?

Unlike the first thing that comes to mind, Interval has nothing to do with time, but a range of values.

If you consider using _ controller, you must keep in mind that it changes the value from lowerBound to upperBound.

In general, these two values are basically defined as lowerBound = 0.0 and upperBound = 1.0, which makes animation calculation easier because [0-> 1] is only a change from 0% to 100%. Therefore, if the total duration of a scene is 10 seconds, it is most likely that after 5 seconds, the corresponding _ controller.value will be very close to 0.5 (= 50%).

If you put 3 different animations on one timeline, you can get the following diagram:

Image.png

If we now consider the interval of values, for each of the three animations, we will get:

MoveLeftToCenter

Duration: 2 seconds, starting with 0 seconds and ending with 2 seconds = > range = [0; 2] = > percentage: from 0% to 20% of the entire scene = > [0.0; 0.20]

MoveCenterToTop

Duration: 3 seconds, starting at 2 seconds, ending at 5 seconds = > range = [2; 5] = > percentage: from 20% to 50% of the entire scene = > [0.20; 0.50]

Disappear

Duration: 5 seconds, starting at 5 seconds, ending at 10 seconds = > range = [5; 10] = > percentage: from 50% to 100% of the entire scene = > [0.50; 1.0]

Now that we have these percentages, we have the definition of each animation, as follows:

/ Definition of the _ controller with a whole duration of 10 seconds///AnimationController _ controller = new AnimationController (duration: const Duration (seconds: 10), vsync: this) / First animation that moves the ball from the left to the center///Animation moveLeftToCenter = new Tween (begin: new Offset (0.0, screenHeight / 2), end: new Offset (screenWidth / 2, screenHeight / 2). Animate (new CurvedAnimation (parent: _ controller, curve: new Interval (0.0,0.20) Curve: Curves.linear,) / Second animation that moves the ball from the center to the top///Animation moveCenterToTop = new Tween (begin: new Offset (screenWidth / 2, screenHeight / 2), end: new Offset (screenWidth / 2,0.0). Animate (new CurvedAnimation (parent: _ controller, curve: new Interval (0.20,0.50) Curve: Curves.linear,) / Third animation that will be used to change the opacity of the ball to make it disappear///Animation disappear = new Tween (begin: 1.0, end: 0.0) .animate (new CurvedAnimation (parent: _ controller, curve: new Interval (0.50,1.0, curve: Curves.linear) ),)) Copy the code

This is all the settings you need to define a scene (or a series of animations). Of course, nothing can stop you from overlapping sub-animations.

Respond to animation statu

Sometimes it is convenient to get the state of the animation (or scene).

Animation may have 4 different states:

Dismissed: the animation stops after it starts (or hasn't started yet)

Forward: animation runs from beginning to end

Reverse: play the animation in reverse

Completed: animation stops after playback

To get this state, we need to listen for changes in the state of the animation in the following ways:

MyAnimation.addStatusListener ((AnimationStatus status) {switch (status) {case AnimationStatus.dismissed:... Break; case AnimationStatus.forward:... Break; case AnimationStatus.reverse:... Break; case AnimationStatus.completed:... Break;}}); copy code

A typical example of state application is state switching. For example, after the animation is completed, we need to reverse it, such as:

MyAnimation.addStatusListener ((AnimationStatus status) {switch (status) {/ When the animation is at the beginning, we force the animation to play / case AnimationStatus.dismissed: _ controller.forward (); break / When the animation is at the end, we force the animation to reverse / case AnimationStatus.completed: _ controller.reverse (); break;}}); copy the code theory is enough, now let's start the actual combat

I mentioned an animation at the beginning of the article, and now I'm going to start implementing it, which is called "guillotine."

Animation analysis and program initialization

In order to achieve the "guillotine" effect in the future, we need to consider several aspects:

The page content itself

When we click the menu icon, the menu bar rotates

When rotated, the menu overrides the page content and fills the entire viewport

Once the menu is fully visible, we click the icon again and the menu rotates to return to its original position and size

From these observations, we can immediately conclude that we are not using a normal Scaffold with AppBar (because the latter is fixed).

We need to use 2 layers of Stack:

Page content (lower layer)

Menu (upper layer)

The basic framework of the program is basically out:

Class MyPage extends StatefulWidget {@ override _ MyPageState createState () = > new _ MyPageState () } class _ MyPageState extends State {@ override Widget build (BuildContext context) {return SafeArea (top: false, bottom: false, child: new Container (child: new Stack (alignment: Alignment.topLeft, children: [new Page (), new GuillotineMenu (),],) }} class Page extends StatelessWidget {@ override Widget build (BuildContext context) {return new Container (padding: const EdgeInsets.only (top: 90.0), color: Color (0xff222222),);}} class GuillotineMenu extends StatefulWidget {@ override _ GuillotineMenuState createState () = > new _ GuillotineMenuState () } class _ GuillotineMenuState extends State {@ overrride Widget build (BuildContext context) {return new Container (color: Color (0xff333333),);}} copy code

The result of this code is a black screen, showing only the GuillotineMenu that covers the entire viewport.

Menu effect analysis

If you look at the example above, you can see that when the menu is fully open, it completely covers the viewport. When opened, only the AppBa is visible.

What if you initially rotate the GuillotineMenu and rotate it π / 2 when the menu button is pressed, as shown in the following figure?

Image.png

We can then rewrite the _ GuillotineMenuState class as follows: (I'm not going to explain how to lay out here, which is not the point.)

Class _ GuillotineMenuState extends State {double rotationAngle = 0; @ override Widget build (BuildContext context) {MediaQueryData mediaQueryData = MediaQuery.of (context); double screenWidth = mediaQueryData.size.width; double screenHeight = mediaQueryData.size.height Return new Transform.rotate (angle: rotationAngle, origin: new Offset (24.0,56.0), alignment: Alignment.topLeft, child: Material (color: Colors.transparent, child: Container (width: screenWidth, height: screenHeight) Color: Color (0xFF333333), child: new Stack (children: [_ buildMenuTitle (), _ buildMenuIcon (), _ buildMenuContent (),],) ),)) } / MenuTitle / Widget _ buildMenuTitle () {return new Positioned (top: 32.0, left: 40.0, width: screenWidth, height: 24.0, child: new Transform.rotate (alignment: Alignment.topLeft, origin: Offset.zero Angle: pi / 2.0, child: new Center (child: new Container (width: double.infinity, height: double.infinity, child: new Opacity (opacity: 1.0, child: new Text ('ACTIVITY') TextAlign: TextAlign.center, style: new TextStyle (color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold, letterSpacing: 2.0,)) ),),) } / MenuIcon / Widget _ buildMenuIcon () {return new Positioned (top: 32.0, left: 4.0, child: new IconButton (icon: const Icon (Icons.menu, color: Colors.white,) OnPressed: () {},),) } / Menu content / Widget _ buildMenuContent () {final List _ menus = [{"icon": Icons.person, "title": "profile", "color": Colors.white,}, {"icon": Icons.view_agenda Title: "feed", "color": Colors.white,}, {"icon": Icons.swap_calls, "title": "activity", "color": Colors.cyan,}, {"icon": Icons.settings "title": "settings", "color": Colors.white,},] Return new Padding (padding: const EdgeInsets.only (left: 64.0,96.0), child: new Container (width: double.infinity, height: double.infinity, child: new Column (mainAxisAlignment: MainAxisAlignment.start) Children: _ menus.map ((menuItem) {return new ListTile (leading: new Icon (menuItem ["icon"], color: menuItem ["color"],) Title: new Text (menuItem ["title"], style: new TextStyle (color: menuItem ["color"], fontSize: 24.0) ) }) .toList (),);}} copy the code

Line 10-13

These lines define the rotation of the guillotine menu around the center of rotation (the position of the menu icon).

The result of this code now displays an unrotated menu screen (because rotationAngle = 0. 0) with a vertical title.

Next, let menu display the animation

If you update the value of rotationAngle (between-π / 2 and 0), you will see that the menu is rotated by the corresponding angle.

As mentioned earlier, we need

A SingleTickerProviderStateMixin, because we only have one scene.

An AnimationController

An animation has an angle change.

The code is as follows:

Class _ GuillotineMenuState extends State with SingleTickerProviderStateMixin {AnimationController animationControllerMenu; Animation animationMenu; / Menu Icon, onPress () handling / _ handleMenuOpenClose () {animationControllerMenu.forward ();} @ override void initState () {super.initState () / Initialization of the animation controller / animationControllerMenu = new AnimationController (duration: const Duration (milliseconds: 1000), vsync: this).. addListener (() {setState (() {});}) / Initialization of the menu appearance animation / / _ rotationAnimation = new Tween (begin:-pi/2.0, end: 0.0) .animate (animationControllerMenu);} @ override void dispose () {animationControllerMenu.dispose (); super.dispose () } @ override Widget build (BuildContext context) {MediaQueryData mediaQueryData = MediaQuery.of (context); double screenWidth = mediaQueryData.size.width; double screenHeight = mediaQueryData.size.height; double angle = animationMenu.value Return new Transform.rotate (angle: angle, origin: new Offset (24.0,56.0), alignment: Alignment.topLeft, child: Material (color: Colors.transparent, child: Container (width: screenWidth, height: screenHeight) Color: Color (0xFF333333), child: new Stack (children: [_ buildMenuTitle (), _ buildMenuIcon (), _ buildMenuContent (),],) ),)) }. / MenuIcon / Widget _ buildMenuIcon () {return new Positioned (top: 32. 0, left: 4. 0, child: new IconButton (icon: const Icon (Icons.menu, color: Colors.white,) OnPressed: _ handleMenuOpenClose,),) }.} copy the code

Now, when we press the menu button, the menu opens, but when we press the button again, the menu does not close. This is what AnimationStatus is going to do.

Let's add a listener and decide whether to run the animation forward or backward based on the AnimationStatus.

/ Menu animation status///enum _ GuillotineAnimationStatus {closed, open, animating} class _ GuillotineMenuState extends State with SingleTickerProviderStateMixin {AnimationController animationControllerMenu; Animation animationMenu; _ GuillotineAnimationStatus menuAnimationStatus = _ GuillotineAnimationStatus.closed; _ handleMenuOpenClose () {if (menuAnimationStatus = = _ GuillotineAnimationStatus.closed) {animationControllerMenu.forward (). OrCancel;} else if (menuAnimationStatus = = _ GuillotineAnimationStatus.open) {animationControllerMenu.reverse (). OrCancel } @ override void initState () {super.initState (); / Initialization of the animation controller / animationControllerMenu = new AnimationController (duration: const Duration (milliseconds: 1000), vsync: this).. addListener (() {setState () {}) }).. addStatusListener ((AnimationStatus status) {if (status = = AnimationStatus.completed) {/ When the animation is at the end, the menu is open / menuAnimationStatus = _ GuillotineAnimationStatus.open } else if (status = = AnimationStatus.dismissed) {/ When the animation is at the beginning, the menu is closed / menuAnimationStatus = _ GuillotineAnimationStatus.closed;} else {/ Otherwise the animation is running / menuAnimationStatus = _ GuillotineAnimationStatus.animating;}}) .}.} copy the code

Now the menu can be opened or closed as expected, but the previous demonstration shows us an on / off animation, which is not linear and seems to have a repeated rebound effect. Next, let's add this effect.

To do this, I will choose the following two effects:

Use bounceOut when the menu is opened

Use bouncIn when the menu is closed

Image.png

Image.png

Class _ GuillotineMenuState extends State with SingleTickerProviderStateMixin {. @ override void initState () {. / Initialization of the menu appearance animation / animationMenu = new Tween (begin:-pi / 2.0, end: 0.0) .animate (new CurvedAnimation (parent: animationControllerMenu, curve: Curves.bounceOut, reverseCurve: Curves.bounceIn,));}.} copy the code

There are still some details not implemented in this implementation: the title disappears when the menu is opened and the title is displayed when the menu is closed. This is an up / out effect and should also be treated as an animation. Let's add it.

Class _ GuillotineMenuState extends State with SingleTickerProviderStateMixin {AnimationController animationControllerMenu; Animation animationMenu; Animation animationTitleFadeInOut; _ GuillotineAnimationStatus menuAnimationStatus . @ override void initState () {/ Initialization of the menu title fade out/in animation / animationTitleFadeInOut = new Tween (begin: 1.0, end: 0.0) .animate (new CurvedAnimation (parent: animationControllerMenu, curve: new Interval (0.0,0.5, curve: Curves.ease,) )) }. / MenuTitle / Widget _ buildMenuTitle () {return new Positioned (top: 32.0, left: 40.0, width: screenWidth, height: 24.0, child: new Transform.rotate (alignment: Alignment.topLeft, origin: Offset.zero, angle: pi / 2.0) Child: new Center (child: new Container (width: double.infinity, height: double.infinity, child: new Opacity (opacity: animationTitleFadeInOut.value, child: new Text ('ACTIVITY', textAlign: TextAlign.center) Style: new TextStyle (color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold, letterSpacing: 2.0,) }.} copy the code

The final effect is basically as follows:

The above is all the contents of the article "how to achieve Flutter Animation". Thank you for reading! I believe we all have a certain understanding, hope to share the content to help you, if you want to learn more knowledge, welcome to follow the industry information channel!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report