我想在 Flutter 中将TextField
与 BLoC 包结合使用。目的是使TextField
的内容与 BLoC 的相应属性保持同步。
由于 BLoC 基于一个基本上订阅Stream
BlocBuilder
小部件,并在Stream
发出新内容时更新和重新呈现所有受影响的子小部件,我认为以下方法就足够了:
BlocBuilder<MyCubit, MyState>(
builder: (BuildContext context, MyState state) {
TextFormField(
initialValue: state.model.title,
onChanged: (String value) => myCubit.setTitle(value),
),
}
);
实际上,它正在起作用。当我希望能够覆盖 BLoC 中的值时,会出现问题。就我而言,我有一个TextField
和一个选择字段(DropdownButton
)。当用户从DropdownButton
中选择一个值时,TextField
的内容应该被覆盖。 使用上述方法,当我在DropdownButton
的onChange
调用setTitle(value)
时,Cubit
中的值被覆盖,但TextField
保持不变。
然后我想到使用一个TextEditingController
来处理TextField
的文本(就像 Felix Angelov 在这里建议的那样),更新更改Cubit
并侦听外部对Cubit
中相应值的更改:
BlocConsumer<MyCubit, MyState>(
listener: (BuildContext context, MyState state) {
if (state is InitialState) {
_controller.addListener(() {
myCubit.setTitle(_controller.text);
});
return;
}
_controller.text = state.title;
},
builder: (BuildContext context, MyState state) {
TextFormField(
controller: _controller,
),
}
);
如果我这样做,那么初始值由Cubit
正确设置,当我使用DropdownButton
更改它时,该值会在TextField
中更新,当TextField
中的文本更改时,该值会在 Cubit 中更新。但是,有一个问题:每次我在TextField
中输入一个字符时,它就会失去焦点。autofocus: true
救援?不幸的是不是。这可以防止失去焦点,但是每当我输入某些内容时,它都会跳转到输入文本的第一个字符。另外,我不希望TextField
最初有焦点。设置FocusNode
也无济于事。 即使像这里建议的那样设置UniqueKey
也不会改变某些内容。 这里提出的关于FocusNode
和onEditingComplete
的想法也没有解决我的问题。
是否有人拥有与 BLoC 结合使用的文本字段的工作解决方案,以满足以下要求:
- 当输入
TextField
中的某些内容时,BLoC/Cubit 会相应地更新 - 当 BloC/Cubit 更新时,
TextField
的内容也会相应更新 - 没有意外的、烦人的键盘关闭行为或失去焦点
TextField
TextField
可以具有初始值,但不必专注于渲染
尽管已经研究了很长一段时间,但我还是找不到解决方案。
在您后来尝试更改TextField
文本时TextEditingController
为什么您的TextField
被包裹在BlocBuilder
内?我相信这可以正确用于从您的 BLoC 更新TextField
的文本:
BlocListener<MyCubit, MyState>(
listener: (BuildContext context, MyState state) {
if(state.title != _controller.text) {
_controller.text = state.title;
_controller.selection = TextSelection.collapsed(offset: state.title.length);
}
},
child: TextFormField(
controller: _controller,
),
);
并且为了使用来自TextField
的事件更新 BLoC 中的值,请在initState
中注册一个TextEditingController
侦听器。
@override
void initState() {
super.initState();
_controller.addListener(_changed);
}
@override
void dispose() {
_controller.removeListener(_changed);
super.dispose();
}
_changed() {
myCubit.setTitle(_controller.text);
}
我认为以下TextField
代码是正确的,除了您可以使用TextEditingController
来设置其初始值。您可以在initState
中设置控制器的初始值,因此您需要使用有状态小部件。
@override
void initState() {
super.initState();
final state = BlocProvider.of<MyCubit>(context, listen: false);
_controller = TextEditingController(text: state.model.title);
}
BlocBuilder<MyCubit, MyState>(
builder: (BuildContext context, MyState state) {
TextFormField(
controller: _controller,
onChanged: (String value) => myCubit.setTitle(value),
),
}
);
在DropdownButton
的onChanged
中,除了在肘上调用setTitle(value)
外,还使用其controller
更改TextField
的值。
(value){
myCubit.setTitle(value);
_controller.text = value;
}
唯一可能仍未解决的问题是,当调用_controller.text = value;
光标移动到开头时 - 在第一个字符之前。
我设法让它工作,而不使用TextSelection.collapsed(offset: state.title.length)
,这限制了文本字段的功能。
控制器应该在状态中定义,而不是在build
函数中定义,并在initState()
中初始化。如果在build
函数中调用addListener
,它将在每次按键后重新初始化控制器。
late TextEditingController inputController;
@override
void initState() {
super.initState();
inputController = TextEditingController();
inputController.addListener(textListener);
}
void textListener() {
context.read<TextCubit>().textChanged(text: inputController.text);
}
在BlocListener
内的build
函数中,如果状态发生变化,应更新inputcontroller.text
。
@override
Widget build(BuildContext context) {
return BlocListener<TextCubit, String>(listener: (_, text) {
if (inputController.text != text) {
inputController.text = text;
}
}, child:
Scaffold(
body: Center(
child: TextField(
controller: inputController,
),
)
));
}