On my signup form, I have a checkbox which needs to shake a bit whenever the user tries to login before accepting the terms and conditions. How can I achieve something like this Flutter?
import 'package:flutter/material.dart';
@immutable
class ShakeWidget extends StatelessWidget {
final Duration duration;
final double deltaX;
final Widget child;
final Curve curve;
const ShakeWidget({
Key key,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
this.child,
}) : super(key: key);
/// convert 0-1 to 0-1-0
double shake(double animation) =>
2 * (0.5 - (0.5 - curve.transform(animation)).abs());
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
key: key,
tween: Tween(begin: 0.0, end: 1.0),
duration: duration,
builder: (context, animation, child) => Transform.translate(
offset: Offset(deltaX * shake(animation), 0),
child: child,
),
child: child,
);
}
}
If you need to re-enable shaking, just change ShakeWidget key to some random one.
double shake(double animation) => sin(animation * pi * 4);
–
Extinction I achieved this a different way because I wanted to be able to control the duration and get a bit more vigorous shaking. I also wanted to be able to add this easily as a wrapper for other child widgets so I looked up how to use keys to have a parent control actions in a child widget. Here is the class:
class ShakerState extends State<Shaker> with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
@override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800), // how long the shake happens
)..addListener(() => setState(() {}));
animation = Tween<double>(
begin: 00.0,
end: 120.0,
).animate(animationController);
}
math.Vector3 _shake() {
double progress = animationController.value;
double offset = sin(progress * pi * 10.0); // change 10 to make it vibrate faster
return math.Vector3(offset * 25, 0.0, 0.0); // change 25 to make it vibrate wider
}
shake() {
animationController.forward(from:0);
}
@override
Widget build(BuildContext context) {
return Transform(
transform: Matrix4.translation(_shake()),
child: widget.child,
);
}
}
And then to use this you need a key in your parent:
final GlobalKey<ShakerState> _shakeKey = GlobalKey<ShakerState>();
And then you can do something like this inside your parent body (see where "Shaker" is used around the child I want to shake):
...
Container(
height: 50,
width: 250,
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () => _handleEmailSignIn(loginController.text, loginPasswordController.text),
child: Shaker(_shakeKey, Text('Login', // <<================
style: TextStyle(color: Colors.white, fontSize: 25),
)),
),
),
...
Then with the controller you can trigger the shake at a time you want programmatically like this (see the use of "_shakeKey"):
Future<void> _handleEmailSignIn(String user, password) async {
try {
await auth.signInWithEmailAndPassword(email: user, password: password);
FocusScope.of(context).unfocus();
await Navigator.pushNamedAndRemoveUntil(context, '/next_page', ModalRoute.withName('/'));
} on FirebaseAuthException catch (e) {
_shakeKey.currentState?.shake(); // <<=============
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
setState(() {});
}
import 'package:vector_math/vector_math_64.dart' as math;
–
Donoho Little improved of @Kent code (added controller).
import 'package:flutter/material.dart';
class ShakeX extends StatefulWidget {
final Widget child;
final double horizontalPadding;
final double animationRange;
final ShakeXController controller;
final Duration animationDuration;
const ShakeX(
{Key key,
@required this.child,
this.horizontalPadding = 30,
this.animationRange = 24,
this.controller,
this.animationDuration = const Duration(milliseconds: 500)})
: super(key: key);
@override
_ShakeXState createState() => _ShakeXState();
}
class _ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
animationController =
AnimationController(duration: widget.animationDuration, vsync: this);
if (widget.controller != null) {
widget.controller.setState(this);
}
super.initState();
}
@override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: widget.animationRange)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(animationController)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
}
});
return AnimatedBuilder(
animation: offsetAnimation,
builder: (context, child) {
return Container(
margin: EdgeInsets.symmetric(horizontal: widget.animationRange),
padding: EdgeInsets.only(
left: offsetAnimation.value + widget.horizontalPadding,
right: widget.horizontalPadding - offsetAnimation.value),
child: widget.child,
);
},
);
}
}
class ShakeXController {
_ShakeXState _state;
void setState(_ShakeXState state) {
_state = state;
}
Future<void> shake() {
print('shake');
return _state.animationController.forward(from: 0.0);
}
}
import 'package:flutter/material.dart';
class ShakeError extends StatefulWidget {
const ShakeError({
Key? key,
required this.child,
this.controller,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
}) : super(key: key);
final Widget child;
final Duration duration;
final double deltaX;
final Curve curve;
final Function(AnimationController)? controller;
@override
State<ShakeError> createState() => _ShakeErrorState();
}
class _ShakeErrorState extends State<ShakeError>
with SingleTickerProviderStateMixin<ShakeError> {
late AnimationController controller;
late Animation<double> offsetAnimation;
@override
void initState() {
controller = AnimationController(duration: widget.duration, vsync: this);
offsetAnimation = Tween<double>(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: widget.curve))
.animate(controller);
if (widget.controller is Function) {
widget.controller!(controller);
}
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
/// convert 0-1 to 0-1-0
double shake(double animation) =>
2 * (0.5 - (0.5 - widget.curve.transform(animation)).abs());
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: offsetAnimation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: Offset(widget.deltaX * shake(offsetAnimation.value), 0),
child: child,
);
},
child: widget.child,
);
}
}
Here is some code from my app. It shakes a red x on the screen. redx.png. I'm sure you could adopt it to your use case. I'm using AnimatedBuilder.
Code in action: https://giphy.com/gifs/Yo2u06oMu1ksPYRD3B
import 'package:flutter/material.dart';
class ShakeX extends StatefulWidget {
const ShakeX({
Key key,
}) : super(key: key);
@override
_ShakeXState createState() => _ShakeXState();
}
class _ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin{
AnimationController controller;
@override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: 24.0).chain(CurveTween(curve: Curves.elasticIn)).animate(controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
controller.forward(from: 0.0);
return AnimatedBuilder(animation: offsetAnimation,
builder: (context, child){
if (offsetAnimation.value < 0.0) print('${offsetAnimation.value + 8.0}');
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.0),
padding: EdgeInsets.only(left: offsetAnimation.value + 30.0, right: 30.0 - offsetAnimation.value),
child: Image.asset("assets/redx.png"),
);
},);
}
}
For those still looking... You can achieve something like that using Animated Widget
ShakeAnimatedWidget(
enabled: this._enabled,
duration: Duration(milliseconds: 1500),
shakeAngle: Rotation.deg(z: 40),
curve: Curves.linear,
child: FlutterLogo(
style: FlutterLogoStyle.stacked,
),
),
Check the link for even advance usage.
There are many animation based packages in flutter, you can check that site (https://fluttergems.dev/animation-transition/) to see them. As developer, you don't have to to create animation classes from scratch.
Regarding shaking animation, I would suggest flutter_animator package. There is a shake widget which exactly performs what you need.
My version on previous code. It prevents usage of paddings and margins
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ShakeX extends StatefulWidget {
final Widget child;
final double horizontalPadding;
final double animationRange;
final ShakeXController shakeXController;
final Duration animationDuration;
const ShakeX({
Key? key,
required this.child,
this.horizontalPadding = 16,
this.animationRange = 16,
required this.shakeXController,
this.animationDuration = const Duration(milliseconds: 500),
}) : super(key: key);
@override
ShakeXState createState() => ShakeXState();
}
class ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin {
late AnimationController animationController;
var childSize = Size.zero;
@override
void initState() {
super.initState();
animationController = AnimationController(duration: widget.animationDuration, vsync: this);
widget.shakeXController.setState(this);
}
@override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation = Tween(begin: 0.0, end: widget.animationRange)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(animationController)
..addStatusListener((status) {
if (status == AnimationStatus.completed && status != AnimationStatus.reverse) {
animationController.reverse();
}
});
return AnimatedBuilder(
animation: offsetAnimation,
builder: (context, child) {
return SizedBox(
width: childSize.width,
height: childSize.height,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
left: offsetAnimation.value,
child: MeasureSize(
onChange: (size) {
setState(() {
childSize = size;
});
},
child: widget.child),
),
],
),
);
},
);
}
}
class ShakeXController {
late ShakeXState _state;
void setState(ShakeXState state) {
_state = state;
}
Future<void> shake() {
return _state.animationController.forward(from: 0.0);
}
}
typedef void OnWidgetSizeChange(Size size);
class MeasureSizeRenderObject extends RenderProxyBox {
Size? oldSize;
OnWidgetSizeChange onChange;
MeasureSizeRenderObject(this.onChange);
@override
void performLayout() {
super.performLayout();
Size newSize = child!.size;
if (oldSize == newSize) return;
oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onChange(newSize);
});
}
}
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key? key,
required this.onChange,
required Widget child,
}) : super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
@override
void updateRenderObject(BuildContext context, covariant MeasureSizeRenderObject renderObject) {
renderObject.onChange = onChange;
}
}
In my case, I needed the ability to control the shake animation outside of the ShakeWidget
. I didn't want to add GlobalKey
to get access to the state of the widget, I don't like to use late
methods if possible and changing keys each time I want to trigger shaking animation also looks hacky to me. So I created a widget that gets a controller to control the shaking animation:
import 'dart:math';
import 'package:flutter/material.dart';
/// A controller that triggers a shake animation
/// Call [shake] to trigger the shake
class ShakeController extends ChangeNotifier {
void shake() => notifyListeners();
}
/// A widget that shakes its child when the controller triggers
/// - [duration] is the duration of the shake
/// - [deltaX] is the maximum distance to shake
/// - [oscillations] is the number of oscillations of the shake
/// - [curve] is the curve of the shake
/// - [controller] is the controller that triggers the shake
/// - [child] is the child to shake
class ShakeWidget extends StatefulWidget {
const ShakeWidget({
super.key,
required this.child,
required this.controller,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 4,
this.oscillations = 6,
this.curve = Curves.linear,
});
final Duration duration;
final double deltaX;
final int oscillations;
final Widget child;
final Curve curve;
final ShakeController controller;
@override
_ShakeWidgetState createState() => _ShakeWidgetState();
}
class _ShakeWidgetState extends State<ShakeWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.duration,
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _animationController, curve: widget.curve),
);
widget.controller.addListener(_startShaking);
}
@override
void dispose() {
widget.controller.removeListener(_startShaking);
_animationController.dispose();
super.dispose();
}
void _startShaking() {
_animationController.forward(from: 0);
}
/// Create a sinusoidal curve that starts and ends at 0
/// Oscillates with increasing amplitude to the middle and then decreasing
/// amplitude to the end.
double _wave(double t) =>
sin(widget.oscillations * 2 * pi * t) * (1 - (2 * t - 1).abs());
@override
Widget build(BuildContext context) => AnimatedBuilder(
animation: _animation,
builder: (context, child) => Transform.translate(
offset: Offset(
widget.deltaX * _wave(_animation.value),
0,
),
child: widget.child,
),
child: widget.child,
);
}
With the linear Curve the movement will be looking like this image:
To start the shaking animation, you just need to call the shake()
method on the controller:
class ShakeExample extends StatelessWidget {
final ShakeController _shakeController = ShakeController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shake Widget Example'),
),
body: Center(
child: ShakeWidget(
controller: _shakeController,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('Shake Me')),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_shakeController.shake();
},
child: Icon(Icons.play_arrow),
),
);
}
}
You can play with the results in this DartPad:
© 2022 - 2024 — McMap. All rights reserved.
SlideTransition
(orAlignTransition
) – OrgyTransform
too but its a little bit complex – Orgy