4 votes

Comment secouer un widget dans Flutter en cas de saisie non valide ?

Sur mon formulaire d'inscription, j'ai une case à cocher qui doit trembler un peu lorsque l'utilisateur essaie de se connecter avant d'avoir accepté les termes et conditions. Comment puis-je réaliser quelque chose comme ça avec Flutter ?

4voto

Vladimir Goldobin Points 23
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,
    );
  }
}

Si vous avez besoin de réactiver le tremblement, il suffit de remplacer la clé ShakeWidget par une clé aléatoire.

2voto

Kent Points 1382

Voici un peu de code de mon application. Il secoue un x rouge sur l'écran. redx.png. Je suis sûr que vous pouvez l'adapter à votre cas d'utilisation. J'utilise AnimatedBuilder.

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

2voto

Rishat Zakirov Points 21

Petite amélioration du code de @Kent (ajout d'un contrôleur).

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

1voto

Eradicatore Points 856

J'y suis parvenu d'une manière différente car je voulais pouvoir contrôler la durée et obtenir une secousse un peu plus vigoureuse. Je voulais également être en mesure d'ajouter facilement cette fonction comme enveloppe pour d'autres widgets enfants. J'ai donc cherché comment utiliser les clés pour qu'un parent contrôle les actions d'un widget enfant. Voici la classe :

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

Et puis pour l'utiliser, vous avez besoin d'une clé dans votre parent :

  final GlobalKey<ShakerState> _shakeKey = GlobalKey<ShakerState>();

Ensuite, vous pouvez faire quelque chose comme ceci à l'intérieur de votre corps parent (voir où "Shaker" est utilisé autour de l'enfant que je veux secouer) :

    ...
    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),
        )),
      ),
    ),
    ...

Ensuite, avec le contrôleur, vous pouvez déclencher le tremblement au moment voulu par programmation, comme ceci (voir l'utilisation de "_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(() {});
  }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X