Using custom curve in Flutter Hero animations
Sometimes the default Hero behavior is not enough and you’d like to add some custom taste to it. One of the simple ways to do it is to customize curve of the transition animation.
In this post you’ll see a sample that allows to use any custom curve to animate bounds of the Hero. In my case it was a bit faster expansion in vertical axis.
If you can’t see the video (on Safari), here’s the GIF.
Hero properties
Hero class has several convenient properties that allow to customize its behavior. These are:
flightShuttleBuilder
that allows to wrap or change entire Hero animation by e.g. adding rotation or fade transitionplaceholderBuilder
that gives us possibility to show Widget in place previously occupied by the animated widgetcreateRectTween
which is a function that determines the bounds of the Hero during animation - that’s what we want to customize today
What is createRectTween
This property takes a CreateRectTween
function which is simply a typedef for Tween<Rect> Function(Rect begin, Rect end)
.
It means that it interpolates two Rects between begin
and end
state. By default a RectTween(begin: begin, end: end)
is used in Hero. It uses its lerp method which aside from few asserts only interpolates Rect coordinates in linear manner:
static Rect lerp(Rect a, Rect b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
if (a == null)
return Rect.fromLTRB(b.left * t, b.top * t, b.right * t, b.bottom * t);
if (b == null) {
final double k = 1.0 - t;
return Rect.fromLTRB(a.left * k, a.top * k, a.right * k, a.bottom * k);
}
return Rect.fromLTRB(
lerpDouble(a.left, b.left, t),
lerpDouble(a.top, b.top, t),
lerpDouble(a.right, b.right, t),
lerpDouble(a.bottom, b.bottom, t),
);
}
The method lerpDouble
is where the interpolation takes place:
double lerpDouble(num a, num b, double t) {
if (a == null && b == null) return null;
a ??= 0.0;
b ??= 0.0;
return a + (b - a) * t;
}
How to change the default implementation
When defining your Hero you should return a custom createRectTween
function like this:
Hero(
tag: index,
createRectTween: (begin, end) {
return CustomRectTween(a: begin, b: end);
},
child: Poster(),
),
And finally CustomRectTween
has to extend RectTween
and to have overwritten lerp
method.
The parameter t
is animation clock value between 0.0 and 1.0. Each Curve
can be used to interpolate any given value between 0.0 and 1.0 on the curve. I use my custom cubic curve in this example.
class CustomRectTween extends RectTween {
CustomRectTween({this.a, this.b}) : super(begin: a, end: b);
final Rect a;
final Rect b;
@override
Rect lerp(double t) {
Curves.elasticOut.transform(t);
//any curve can be applied here e.g. Curve.elasticOut.transform(t);
final verticalDist = Cubic(0.72, 0.15, 0.5, 1.23).transform(t);
final top = lerpDouble(a.top, b.top, t) * (1 - verticalDist);
return Rect.fromLTRB(
lerpDouble(a.left, b.left, t),
top,
lerpDouble(a.right, b.right, t),
lerpDouble(a.bottom, b.bottom, t),
);
}
double lerpDouble(num a, num b, double t) {
if (a == null && b == null) return null;
a ??= 0.0;
b ??= 0.0;
return a + (b - a) * t;
}
}
It’s important to note that I wanted to change only top
coordinate of the Hero’s Rect, so when calculating the value I use (1 - verticalDist)
(thus inverting the curve).
I hope you’ll find it useful and try to experiment with Hero widget on your own.
I highly recommend the post Mastering Hero Animations in Flutter by Chema Molins from Flutter Community.