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 ?
Réponses
Trop de publicités?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.
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"),
);
},);
}
}
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);
}
}
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(() {});
}