'How to shake a widget in Flutter on invalid input?

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?



Solution 1:[1]

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(() {});
  }

Solution 2:[2]

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"),
                  );
    },);
  }
}

Solution 3:[3]

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);
  }
}

Solution 4:[4]

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
  _ShakeErrorState 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,
    );
  }
}

Solution 5:[5]

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. Shaking flutter logo

Solution 6:[6]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eradicatore
Solution 2 Kent
Solution 3 Rishat Zakirov
Solution 4 TurboCoding
Solution 5 Mathias Godwin
Solution 6 Utku A.