This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
preface
Recently, I often use the animation Curve class Curve when writing animation-related chapters. How is this class implemented? What if you want to create your own custom animation curve? Let’s find out.
The Curve class definition
The Curve class is defined as follows:
abstract class Curve extends ParametricCurve<double> {
const Curve();
@override
double transform(double t) {
if (t == 0.0 || t == 1.0) {
return t;
}
return super.transform(t);
}
Curve get flipped => FlippedCurve(this);
}
Copy the code
It doesn’t look like anything is defined, but there are two things that we’re doing here, one is explicitly double, and the other is overloading transform, and we’re just doing special processing for t to make sure that t is in the range of 0 to 1, And the starting and ending values 0.0 and 1.0 are not converted by the conversion function. This is primarily defined by the action Triccurve at the upper level. The documentation suggests that subclasses reload the transformInternal method, so we’ll move on to the action action class, ParametricCurve, which looks like this:
abstract class ParametricCurve<T> {
const ParametricCurve();
T transform(double t) {
assert(t ! =null);
assert(t >= 0.0 && t <= 1.0.'parametric value $t is outside of [0, 1] range.');
return transformInternal(t);
}
@protected
T transformInternal(double t) {
throw UnimplementedError();
}
@override
String toString() => objectRuntimeType(this.'ParametricCurve');
}
Copy the code
As you can see, the transform method, in addition to doing validation, actually calls the transformInternal method, so subclasses must implement this method or they will raise an UnimplementedError exception.
Instance analysis
As you can see from the source code above, the key is the parameter t. What does this parameter t represent? It says in the notes:
Returns the value of the curve at point T. – Returns the value of the curve at point T.
Therefore, t can be considered as the abscissa of the curve, and to ensure the consistency of the curve, the normalization is done, that is, the value of t is between 0 and 1. This may seem a bit abstract, but let’s look at two examples for comparison, starting with the simplest implementation of Curves. Linear.
class _Linear extends Curve {
const _Linear._();
@override
double transformInternal(double t) => t;
}
Copy the code
It’s super simple, just return t, and the corresponding function to our math is:
y = f(t) = t
Copy the code
The corresponding curve is an oblique line. In other words, within the set animation time, the linear transition from 0 to 1 will be completed, that is, the change is uniform. Linearity is easy to understand, but let’s look at another implementation of deceleration curve activity.
class _DecelerateCurve extends Curve {
const _DecelerateCurve._();
@override
double transformInternal(double t) {
t = 1.0 - t;
return 1.0- t * t; }}Copy the code
Let’s look at what _activity is going to look like.
Recall our high school physics uniform deceleration motion, acceleration is negative (that is, deceleration) distance calculation formula:
The deceleration curve above can actually be viewed as the deceleration of the initial velocity of 2 and the acceleration of 2. The reason why it’s 2 is because t is in the range from 0 to 1, so it’s still going to be in the range from 0 to 1. You might ask, why do I have to make sure that the curve is 0 minus 1? Let’s suppose what happens if the calculation is not 0-1, let’s say we want to move a component 60 pixels on the screen. Assume that the animation curve does not start at 0. That means the initial distance traveled is jumping. Likewise, if the end value is 1.0, means that in the final point distance value is 60.0, that means the end of the needs from the last point to the final 60 pixel position (animation need to make sure that the ultimate mobile distance is 60 pixels) that means the animation will be the effect of jump, the curve plotting words will be like (green is normal, The red line is abnormal). The animation experience is terrible! Therefore, this is a key point, if your custom curve’s transformInternal method does not return values in the range of 0-1, it means that the animation will jump, causing the animation to feel frameless.
With this foundation, we can explain the basic mechanism of animation Curve, which is actually the transition from the initial state to the end state of the component within the given Duration of animation. This transition is accomplished along the set Curve class, whose abscissa is 0-1.0. The curve starts at 0 and ends at 1.0, while the median can be below 0 or above 1. We can imagine that we follow a set curve and eventually reach our destination no matter what. The curve controls how we go, how many turns we take, and how fast we change. But if your curve doesn’t start at zero or end at one, it’s like jumping off a cliff!
Sinusoidal animation curve
Let’s do a sinusoidal animation to verify that.
class SineCurve extends Curve {
final int count;
const SineCurve({this.count = 1}) : assert(count > 0);
@override
double transformInternal(double t) {
return sin(2* count* pi * t); }}Copy the code
The count parameter is used to control the cycle, that is, how many more rounds can be made before the destination is reached. Here we find that the initial value is 0, but the end value of a period (2π) is also 0, so that a jump occurs before the animation ends. Take a look at the sample code, which moves the circle down 60 pixels.
AnimatedContainer(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(30.0), ), transform: Matrix4.identity().. translate(0.0, up ? 60.0 : 0.0.0.0),
duration: Duration(milliseconds: 3000),
curve: SineCurve(count: 1),
child: ClipOval(
child: Container(
width: 60.0,
height: 60.0,
color: Colors.blue,
),
),
)
Copy the code
The effect is as follows, notice that the last frame jumps directly from 0 to 60.
So how do we adjust this? Let’s see what the sine curve looks like.
If we want to meet the requirement of 0-1, we have to move another 90 degrees to get there. However, there is a problem with this because it breaks the periodicity of things like Settingscount=2
It turned out wrong again. If we look at the pattern, we actually only need to move 90 degrees more for the first cycle (the point of the arrow on the way), and 360 degrees for the rest of the cycle (2π). The Angle is actually 2.5π, 4.5π, 6.5π… The corresponding Angle formula is actually:
So the adjusted sine curve code is:
class SineCurve extends Curve {
final int count;
const SineCurve({this.count = 1}) : assert(count > 0);
@override
double transformInternal(double t) {
// We need to compensate PI /2 angles so that the starting value is 0 and the ending value is 1 to avoid a sudden return to 0 at the end
return sin(2 * (count + 0.25) * pi * t); }}Copy the code
Look at the effect after adjustment, is it smooth transition?
conclusion
This article introduces the principle of Flutter animation Curve class and the control mechanism of Flutter animation. In fact, Curve class is to complete the transition from the start to the end along the Curve within the specified time. However, to ensure smooth transitions, you should ensure that the start and end values of the custom curve’s transformInternal method return values are 0 and 1, respectively.
Bonus: Reviews draw nuggets around
According to the digging star activity, comments can draw gold digging gifts! Quick, what’s your favorite animation curve to use?
I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder.
👍🏻 : feel the harvest please point a praise to encourage!
🌟 : Collect articles, easy to look back!
💬 : Comment exchange, mutual progress!