Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 755
Panduranga Rao Sadhu Avatar asked Dec 01 '19 07:12

Panduranga Rao Sadhu


2 Answers

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.

like image 89
Vladimir Goldobin Avatar answered Nov 15 '22 10:11

Vladimir Goldobin


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(() {});
  }
like image 39
Eradicatore Avatar answered Nov 15 '22 10:11

Eradicatore