如何在无效输入时摇动 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) => 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 小部件的颜色?