Почему курсор в TextField переходит в начало текста всякий раз, когда срабатывает событие onChanged?

В моем приложении есть (помимо прочего) TextField и ElevatedButton. Я управляю состоянием с помощью Provider. Я хочу, чтобы цвет кнопки менялся с белого на синий при изменении содержимого TextField.

У меня есть этот код в TextField:

 onChanged: (_) {
   context.read<Data>().changeButtonColor();
   },

Это в ElevatedButton:

style: ElevatedButton.styleFrom(
   backgroundColor: context.watch<Data>().buttonColor),

И это в Провайдере:

Color buttonColor = Colors.white;
void changeButtonColor() {
  buttonColor = Colors.blue;
  notifyListeners();
}

Цвет кнопки изменится, как и ожидалось. Однако у меня проблема в том, что курсор в TextField переходит в начало текста, как только срабатывает событие onChanged. Это делает приложение непригодным для использования.

Почему курсор делает это? И как мне сделать так, чтобы код работал как надо, т. е. без перехода курсора TextField в начало текста?


Обновлено: родительский метод build() включает в себя

Note? selNote = context.watch<Data>().selectedNote;
noteTextController.text = selNote.content;

Для справки, вот полный код двух виджетов:

// Note Text TextField---------------------------------
              Padding(
                padding: EdgeInsets.only(
                  top: 15,
                  bottom: 15,
                ),
                child: TextField(
                  controller: noteTextController,
                  keyboardType: TextInputType.multiline,
                  maxLines: null,
                  minLines: 10,
                  decoration: InputDecoration(
                    border: InputBorder.none,
                    fillColor: Colors.white,
                  ),
                  onChanged: (_) {
                    context.read<Data>().changeButtonColor();
                  },
                ),
              ),

 // Save Button------------------------------------------
              Padding(
                padding: EdgeInsets.only(
                  top: 15,
                  bottom: 15,
                ),
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                      backgroundColor: context.watch<Data>().buttonColor),
                  onPressed: () async {
                    selNote = context.read<Data>().selectedNote;

                    var title = noteTitleController.text;
                    var content = noteTextController.text;
                    // If this is a new note ... create it
                    if (selNote == null) {
                      context.read<Data>().add(title, content);
                    } else {
                      // If this is an already existing note ... update it
                      selNote!.title = title;
                      selNote!.content = content;
                      context.read<Data>().update(title: title, content: content);
                    }
                  },
                  child: Text('Save'),
                ),
              ),

Как определить контроллер? Как выглядит родительский виджет? Кроме того, можете ли вы попробовать добавить ключ к TextField?

Dhafin Rayhan 13.04.2024 03:31

@DhafinRayhan Я отредактировал вопрос. Два виджета Padding выше являются дочерними элементами ListView,, который является дочерним элементом Container внутри ListView.

Al C 13.04.2024 19:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что вы меняете текст контроллера внутри метода сборки. Каждый раз, когда ваш TextField вызывает обратный вызов onChanged, значение цвета кнопки в провайдере будет обновляться, поэтому, когда его просматривают в том же виджете, он перезапускает метод build, который вызывает обновление текста контроллера, следовательно, переход курсора.

Значение TextField должно быть локальным, и вам не следует вручную обрабатывать изменение text, если источником изменения является сам TextField. Просто полагайтесь на внутренние изменения контроллера, и если вы хотите «отразить» состояние до более глобального состояния, вы можете сохранить обратный вызов onChanged. Вам не нужно считывать значение обратно в контроллер.

В более общем смысле, вам не следует вызывать установщик controller.text декларативным/реактивным способом. Его следует вызывать только в императивном порядке.


Некоторые связанные статьи:

Во-первых, спасибо за объяснение. Первые два абзаца очень полезны. Если у вас есть время, не могли бы вы немного расширить последний абзац? Я не уверен, что это значит. Я просто знаю, что рассматривал такие вещи, как Flutter’s Provider, как способ отделить пользовательский интерфейс от бизнес-логики, и подумал, что мне нужно использовать контроллеры для управления текстом TextField.

Al C 14.04.2024 17:02

Когда вы передаете TextEditingControllerTextField, он самостоятельно обрабатывает обновление сохраненного text. По сути, наличие этого контроллера внутри State из StatefulWidget — это уже вопрос управления состоянием.

Dhafin Rayhan 14.04.2024 17:47

PS Я прочитал [это] (freecodecamp.org/news/…), чтобы помочь мне понять в целом.

Al C 14.04.2024 17:47

Во Flutter существует «эфемерное состояние», которое обрабатывает локальное состояние виджета, и «состояние приложения», которое обрабатывает состояния всего приложения. TextEditingController всегда должно быть эфемерным состоянием. Независимо от того, хотите ли вы, чтобы значение было доступно для состояния приложения, это не имеет значения, контроллер все равно должен обрабатывать состояние самостоятельно. Затем вы можете создать новое состояние всего приложения и скопировать в него состояние контроллера, но не заставлять контроллер считывать значение из состояния приложения.

Dhafin Rayhan 14.04.2024 17:47

Код внутри build не должен иметь побочных эффектов, таких как императивное действие, подобное тому, как вы приказываете контроллеру обновить свое значение. Его следует использовать только для рендеринга виджета и, вероятно, выполнять некоторые чистые вычисления, чтобы получить некоторые вычисленные значения, которые необходимо передать виджетам. С другой стороны, императивные методы могут иметь побочные эффекты, примерами которых являются onTap или onPressed обратный вызов кнопок и детекторов жестов.

Dhafin Rayhan 14.04.2024 17:49

Также обратите внимание, что среда Flutter может вызывать метод build сколько угодно раз, и ожидается, что он будет вызывать метод build до 60-120 раз в секунду, поэтому он должен быть чистым (но, конечно, из-за оптимизации , обычно такого не бывает).

Dhafin Rayhan 14.04.2024 17:51

Да, кстати, я добавил в ответ две ссылки, которые могут быть связаны. Надеюсь, это поможет!

Dhafin Rayhan 14.04.2024 17:54

Потрясающий. Оцените полноту вашего ответа(ов). Спасибо!

Al C 14.04.2024 19:16

PS Для тех, кто увидит это сообщение позже, я не знал, когда писал его, что когда вы программно устанавливаете значение текстового контроллера, присваивая значение его текстовому свойству, курсор перейдет в начало текста. В этом суть вопроса, о котором я спрашивал. <br> PPS Еще один источник путаницы для меня связан с проблемой всего приложения и эфемерного состояния. Поскольку текст TextField также отображается в других виджетах, я рассматриваю его как состояние всего приложения.

Al C 14.04.2024 19:42

Другие вопросы по теме