文本字段和 BLoC - 值未更新或键盘已关闭



我想在 Flutter 中将TextField与 BLoC 包结合使用。目的是使TextField的内容与 BLoC 的相应属性保持同步。

由于 BLoC 基于一个基本上订阅StreamBlocBuilder小部件,并在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的内容应该被覆盖。 使用上述方法,当我在DropdownButtononChange调用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也不会改变某些内容。 这里提出的关于FocusNodeonEditingComplete的想法也没有解决我的问题。

是否有人拥有与 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),
),
}
);

DropdownButtononChanged中,除了在肘上调用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,
),
)
));
}

最新更新