如何在无效输入时摇动 Flutter 中的小部件?

Posted

技术标签:

【中文标题】如何在无效输入时摇动 Flutter 中的小部件?【英文标题】:How to shake a widget in Flutter on invalid input? 【发布时间】:2020-03-26 03:41:40 【问题描述】:

在我的注册表单上,我有一个复选框,当用户在接受条款和条件之前尝试登录时,我需要稍微晃动一下。我怎样才能实现这样的 Flutter?

【问题讨论】:

SlideTransition(或AlignTransition 你也可以使用Transform,但是有点复杂 【参考方案1】:
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,
    );
  

如果您需要重新启用摇动,只需将 ShakeWidget 键更改为某个随机键即可。

【讨论】:

这如何回答这个问题?我知道这会震动小部件,但它不能应用于无效输入。 shakeWidget.key = UniqueKey();给出错误 'key' can't be used as a setter because it's final. 如何循环重复摇动动画? 我发现使用它更好:double shake(double animation) =&gt; sin(animation * pi * 4); 效果很好。但是除了换键还有其他方法可以触发抖动效果吗?【参考方案2】:

这是我的应用程序中的一些代码。它在屏幕上晃动一个红色的 x。 redx.png。我相信你可以将它应用到你的用例中。我正在使用 AnimatedBuilder。

实际代码: 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"),
                  );
    ,);
  

【讨论】:

【参考方案3】:

@Kent 代码稍有改进(添加了控制器)。

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

【讨论】:

有没有简单的修改让它可以绑定几次?【参考方案4】:

我以不同的方式实现了这一点,因为我希望能够控制持续时间并获得更剧烈的震动。我还希望能够轻松地将其添加为其他子小部件的包装器,因此我查找了如何使用键在子小部件中进行父控件操作。这是课程:

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

然后要使用它,您需要父母的钥匙:

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

然后你可以在你的父母体内做这样的事情(看看我想摇晃的孩子周围用了“Shaker”的地方):

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

然后使用控制器,您可以像这样以编程方式在您想要的时间触发摇晃(请参阅“_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(() );
  

【讨论】:

查看如何使用按键控制子部件:stacksecrets.com/flutter/…【参考方案5】:
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,
    );
  


【讨论】:

【参考方案6】:

对于那些仍在寻找的人...您可以使用 Animated Widget 实现类似的目标

ShakeAnimatedWidget(
      enabled: this._enabled,
      duration: Duration(milliseconds: 1500),
      shakeAngle: Rotation.deg(z: 40),
      curve: Curves.linear,
      child: FlutterLogo(
        style: FlutterLogoStyle.stacked,
      ),
    ),

检查link 以获得更高级的使用。

【讨论】:

以上是关于如何在无效输入时摇动 Flutter 中的小部件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 中的小部件下设置所有 Text 小部件的颜色?

如何在 Flutter 中移动屏幕中的小部件

Flutter ListView 与 List 中的小部件不会在应该更新时更新

如何从 Flutter-web 中的小部件创建图像?

如何将值从initstate传递给flutter中的小部件

在 Flutter 中的小部件之间传递数据的最佳方式