Flutter Keyboard показать / закрыть вызвать перестройку виджета?
У меня есть экран, который содержит Form
с StreamBuilder
когда я загружаю исходные данные из StreamBuilder
, TextFormField
показать данные, как ожидалось. Когда я нажимаю TextFormField
Клавиатура показывает эту причину перестроить виджет, затем снова, после редактирования, клавиатура выключается, эта причина снова перестраивает виджет, и когда виджет перестраивается StreamBuilder
снова подписывается и значения текстового поля заменяются начальным значением.
Вот мой код
Widget _formUI() {
return StreamBuilder<ConcreteEstimationForm>(
stream: _bloc.inputObservable(),
builder: (context, AsyncSnapshot<ConcreteEstimationForm> snapshot) {
if (snapshot.hasData) {
ConcreteEstimationForm form = snapshot.data;
_descriptionController.text = form.description;
return Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: "Description"),
keyboardType: TextInputType.text,
validator: _descriptionValidator,
controller: _descriptionController,
onSaved: (String value) {
_description = value;
},
),
],
);
} else {
return new Center(
child: new CircularProgressIndicator(),
);
}
});
}
Кто-нибудь сталкивался с этой проблемой? Как это решить?
Благодарю.
3 ответа
Клавиатура, вызывающая перестроения
Это имеет смысл и ожидается , что открытие программной клавиатуры вызовет перестроение. За кулисами обновляется вставками вида . Эти
Я могу с уверенностью предположить, что вы используетев вашем приложении Flutter. Как и многие другие виджеты фреймворка, виджеты зависят (см.InheritedWidget
) на(который получает данные изWindow
содержащий ваше приложение) с помощью
ВидетьMediaQueryData
Чтобы получить больше информации.
Все сводится к
Короче говоря, скаффолд, адаптирующийся к скорректированным вставкам представления, требует перестройки пользовательского интерфейса скаффолда. И поскольку ваши виджеты обязательно являются потомками скаффолда (вероятнее всего,
Вы можете отключить изменение размера вставок представления, используяScaffold.resizeToAvoidBottomInset
. Однако это не обязательно остановит перестроения, так как все еще может существовать зависимость от
Идемпотентные методы сборки
Вы всегда должны создавать свои виджеты Flutter таким образом, чтобы ваши методы были идемпотентными .
Парадигма заключается в том, что вызов сборки может произойти в любой момент времени , до 60 раз в секунду (или больше, если на более высокой частоте обновления).
Что я имею в виду под вызовами идемпотентной сборки , так это то, что когда ничего о конфигурации вашего виджета (в случае
Открытие программной клавиатуры, вызывающее перестроение, — просто хороший пример того, почему это так. Другими примерами являются поворот устройства, изменение размера в Интернете, но на самом деле это может быть что угодно, поскольку ваше дерево виджетов начинает становиться сложным (подробнее об этом ниже).
переподписка на восстановление
Возвращаясь к исходному вопросу: в данном случае ваша проблема в том, что вы неправильно подходите к . Вы не должны кормить его потоком, который воссоздается при каждой сборке.
Строители потоков работают следующим образом: подписка на первоначальный поток и повторная подписка всякий раз, когда поток обновляется. Это означает, что когда
Вы можете увидеть это в
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
Очевидное решение здесь заключается в том, что вы захотите предоставить один и тот же поток между различными вызовами сборки, когда вы не хотите повторно подписываться. Это восходит к вызовам идемпотентной сборки !
А
Таким образом, неисправная функция в вашем случае
Заметки
Обратите внимание, я сказал, что вызовы сборки могут происходить «в любой момент времени». На самом деле вы можете (технически) точно контролировать, когда каждая сборка будет происходить в вашем приложении. Однако обычное приложение будет настолько сложным, что вы не сможете его контролировать, поэтому вам понадобятся идемпотентные вызовы сборки.
Клавиатура, вызывающая перестроения, является хорошим примером для этого.
Если вы думаете об этом на высоком уровне, это именно то, что вам нужно - фреймворк и его виджет (или виджеты, которые вы создаете) заботятся о том, чтобы реагировать на внешние изменения и перестраиваться, когда это необходимо. Ваши листовые виджеты в дереве не должны заботиться о том, происходит ли перестроение - они должны быть хорошо помещены в любую среду, а фреймворк позаботится о реагировании на изменения в этой среде путем соответствующей перестройки.
Надеюсь, я смог прояснить это для вас :)
Я столкнулся с подобной проблемой в своем приложении. Что решило мою проблему, так это очистить мое «дерево виджетов», как предложил один из программистов на этом форуме.
Попробуйте переместить определение вашего потока в состояние инициализации. Это предотвратит отключение и повторное подключение вашего потока каждый раз, когда происходит перестроение.
var datastream;
@override
void initState() {
dataStream = _bloc.inputObservable();
super.initState();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: dataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return TextFormField(
// ...
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
Это можно решить, создав виджет с состоянием, например следующий
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Widget child;
const StatefulWrapper({@required this.onInit, @required this.child});
@override
_StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
@override
void initState() {
if (widget.onInit.call != null) {
widget.onInit();
}
super.initState();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
и обертывание виджета без состояния с помощью обертки
Widget body;
class WidgetStateless extends StatelessWidget {
WidgetStateless();
@override
Widget build(BuildContext context) {
return StatefulWrapper(
onInit: () async {
//Create the body widget in the onInit
body = Container();
},
child : body
)
}