flutter - 为什么在具有另一个 setState 的子小部件内的回调中调用 setState 会破坏程序?
问题描述
这是我使用动画实现的自定义 Switch 小部件。
enum SwitchType {
LockToggle, EnableToggle
}
class DiamondSwitch extends StatefulWidget {
final double width, height;
final SwitchType switchType;
final double switchThumbSize;
final VoidCallback onTapCallback;
DiamondSwitch({
key, this.width, this.height,
this.switchType, this.switchThumbSize,
@required this.onTapCallback
}) : super(key: key);
@override
_DiamondSwitchState createState() => _DiamondSwitchState(
width: width, height: height,
switchType: switchType, switchThumbSize: switchThumbSize,
onTapCallback: onTapCallback
);
}
class _DiamondSwitchState extends State<DiamondSwitch> {
final double width, height;
final int _toggleAnimationDuration = 1000;
bool _isOn = false;
final List<Color>
_darkGradientShades = <Color>[
Colors.black, Color.fromRGBO(10, 10, 10, 1.0)
],
_lightGradientShades = <Color>[
Colors.white, Color.fromRGBO(150, 150, 150, 1.0)
];
final SwitchType switchType;
final double switchThumbSize;
List<Icon> _switchIcons = new List<Icon>();
final double _switchIconSize = 35.0;
final VoidCallback onTapCallback;
_DiamondSwitchState({
this.width = 100.0, this.height = 40.0,
this.switchThumbSize = 40.0, @required this.switchType,
@required this.onTapCallback
});
@override
void initState() {
_switchIcons.addAll(
(switchType == SwitchType.LockToggle)?
<Icon>[
Icon(
Icons.lock,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.lock_open,
color: Colors.white,
size: _switchIconSize,
)
]
:
<Icon>[
Icon(
Icons.done,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.close,
color: Colors.white,
size: _switchIconSize,
)
]
);
super.initState();
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: _toggleAnimationDuration),
width: width, height: height,
decoration: ShapeDecoration(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
side: (_isOn)?
BorderSide(
color: Color.fromRGBO(45, 45, 45, 1.0),
width: 0.5,
)
:
BorderSide.none,
),
gradient: LinearGradient(
colors: <Color>[
...((_isOn)? _darkGradientShades : _lightGradientShades)
],
begin: Alignment(1.0, -0.8), end: Alignment(-0.7, 1.0),
stops: <double>[0.4, 1.0]
),
),
alignment: Alignment(0.0, 0.0),
child: Stack(
alignment: Alignment(0.0, 0.0),
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: _toggleAnimationDuration),
curve: Curves.easeIn,
left: (_isOn)? 0.0 : ((width * 70) / 100),
right: (_isOn)? ((width * 70) / 100) : 0.0,
child: GestureDetector(
onTap: () {
onTapCallback();
setState(() {
_isOn = !_isOn;
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: _toggleAnimationDuration),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: Transform.rotate(
alignment: Alignment.center,
angle: (math.pi / 4),
child: Container(
width: switchThumbSize, height: switchThumbSize,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.0),
color: (_isOn)? Colors.white : Colors.black,
border: (_isOn)?
null
:
Border.all(
color: Color.fromRGBO(87, 87, 87, 1.0),
width: 1.0,
),
),
child: Transform.rotate(
alignment: Alignment.center,
angle: -(math.pi / 4),
child: (_isOn)?
_switchIcons[0]
:
_switchIcons[1],
),
),
),
),
),
)
],
),
);
}
}
我添加了一个 onTapCallback,因为我需要在父小部件中设置另一个标志来触发图像更改。这是属于父小部件的相关代码;
DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
当我运行此代码时,动画不起作用。它检测到点击并执行onTap回调,以及onTap Works 中的所有代码(我使用打印方法进行了测试),但正如我所说,动画没有发生。
我想了解为什么会发生这种情况,这是关于 Flutter 的工作原理吗?如果是,你能解释一下吗?
TY抽出时间^.^!
编辑
我想知道为什么使用 setState 获取方法会破坏我正在使用应答器@pulyaevskiy 实现的方法共享当前父小部件的动画。
class _SettingsState extends State<Settings> {
bool
_isLockOn = false,
_isPassiconOn = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
children: <Widget>[
/*Lock Toggle Graphic*/
Align(
alignment: Alignment(-0.1, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
(_isLockOn)? "locked" : "unlocked",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Lock Toggle Image*/
Image.asset(
"assets/images/settings_screen/crystal_"
"${(_isLockOn)? "white_light_up" : "black_outline"}.png",
scale: 5.0,
alignment: Alignment.center,
),
],
),
),
/*Lock Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 12.5),
child: DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isLockOn,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
/*Passicon Toggle Graphic*/
Align(
alignment: Alignment(-0.32, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
"passicon",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Passicon Toggle Image*/
Padding(
padding: const EdgeInsets.only(left: 40),
child: Image.asset(
"assets/images/settings_screen/emote_"
"${(_isPassiconOn)? "winking" : "nervous"}.png",
scale: 3.25,
alignment: Alignment.center,
),
),
],
),
),
/*Passicon Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 42.5),
child: DiamondSwitch(
switchType: SwitchType.PassiconToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isPassiconOn,
onTapCallback: () {
this.setState(() {
this._isPassiconOn = !this._isPassiconOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
],
)
),
);
}
}
我添加并在asflagToControl
中DiamondSwitch
使用它。_DiamondSwitchState
bool get _isOn => widget.flagToControl;
在旧方法中,如果我不执行其他事情
setState() { _isOn = !_isOn; }
动画按原样发生。我错过了什么?
解决方案
在您onTapCallback
的情况下,您正在更改父小部件的状态,这确实有效。但是它不会影响DiamondSwitch
小部件本身的状态(我看到在 GestureDetector 中您还设置了开关小部件的状态,但这种方法存在一些问题)。
要解决此问题,您可以将this._isLockOn
父小部件状态的值传递给子小部件DiamondSwitch
。这意味着您需要开关小部件上的另一个属性。例如
class DiamondSwitch extends StatefulWidget {
final bool isOn;
// ... remaining fields go here
DiamondSwitch({this.isOn, ...});
}
然后_DiamondSwitchState
也改变。可以简单地代理_isOn
小部件的值:
class _DiamondSwitchState extends State<DiamondSwitch> {
bool get _isOn => widget.isOn;
}
isOn
这比您现在在两个地方(在父小部件和开关本身中)保持状态要好得多。通过此更改,您的isLockOn
状态仅保留在父小部件上,您只需将其传递给切换子小部件即可使用。
这意味着对于 GestureDetector 的onTap
属性,您也只需传递onTapCallback
父小部件的 ,无需用另一个函数包装它。
最后一部分:在您的父小部件的构建方法中:
DiamondSwitch(
isOn: this._isLockOn, // <-- new line here to pass the value down
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
这样做的另一个好处是,现在您可以根据需要使用不同的默认值初始化您的开关(现在硬编码为始终以 开头false
)。因此,如果您从数据库加载值isLockOn
并将其设置为,true
您可以立即将此值传递给 switch 子节点并正确表示您的状态。
推荐阅读
- ios - 如何修复 TableViewCell 约束冲突?
- python - 函数在while循环后不执行
- magento2 - Magento 2 仅将 /home 添加到主页 url
- python - 我正在尝试使用以下代码获取数据,但它给出了错误是否有任何其他方法
- windows - 如何在 Windows 启动时添加 mongoDB 服务?
- ffmpeg - 每秒生成一个 .m4s 文件的选项
- python-3.x - 如何使用 python 替换命令中的源和目标属性名称。有超过 500 种不同的属性可以替换
- php - Codeigniter Ajax Dependent Dropdown 在 foreach 语句中不起作用
- bash - Postgres 函数创建中 $$ 双美元的语法错误
- android - Mac 上没有 Android Studio 的 AVD Manager GUI