Flutter TextField TextFieldController setState - позиция изменения курсора - PullRequest
0 голосов
/ 20 февраля 2020

Недавно я написал нужную мне тестовую программу, которая по сути является программой CRUD. Мне нужно было обработать это иначе, чем другим аналогичным программам, которые я написал, потому что я обычно использую виджет FAB с состоянием и не должен устанавливать setState () для включения и отключения FAB. В этой тестовой программе я не хотел использовать пользовательский FAB, а использовал стандартный FAB. Я обнаружил, что всякий раз, когда мне приходилось включать или отключать FAB из-за изменения TextField, для этого требовался setState (), и после сборки курсор для редактируемого TextField перемещался. Я не знаю, почему это происходит, потому что я не воссоздал виджеты. Единственное решение, которое я мог найти для решения этой проблемы, было довольно грязным и требовало сохранения позиции виджета в списке TextField, а также сохранения выделения, а затем после сборки сбрасывать выделение в сохраненное выделение.

Мне нужно, чтобы FAB включался только при изменении данных. Очевидно, что это может варьироваться с каждой ключевой записью.

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

----- Добавили код ниже ----

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() => runApp(MyApp());

//=====================================================================================

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test Widgets',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(title: 'Test Widgets'),
    );
  }
}

//=====================================================================================

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _HomePageState createState() => _HomePageState();
}

//=====================================================================================

class _HomePageState extends State<HomePage> {
  bool _tfDataHasChanged = false;
  bool _tfInitialized = false;
  bool _tfSaveSelection = false;
  int _iNdxWidgetChanged = -1;
  List<String> _lsOldData = ['Row 1', 'Row 2', 'Row 3', 'Row 4'];
  List<String> _lsNewData = ['Row 1', 'Row 2', 'Row 3', 'Row 4'];
  List<TextField> _lwTextFields;
  TextSelection _wTextSelection;

//-------------------------------------------------------------------------------------

  @override
  void dispose() {
    for (int iNdxWidget = 0;
        _lwTextFields != null && iNdxWidget < _lwTextFields.length;
        iNdxWidget++) {
      _lwTextFields[iNdxWidget].focusNode.removeListener(() {
        _fnFocusChanged();
      });
      _lwTextFields[iNdxWidget]?.controller?.dispose();
      _lwTextFields[iNdxWidget]?.focusNode?.dispose();
    }
    super.dispose();
  }

//-------------------------------------------------------------------------------------

  @override
  Widget build(BuildContext context) {
    _tfInitialized = false;
    SchedulerBinding.instance.addPostFrameCallback((_) => _fnOnBuildComplete());
    if (_lwTextFields == null) {
      _fnCreateAllWidgets();
    }
    List<Widget> lwDisplay = _fnCreateDisplay();
    return Scaffold(
      appBar: AppBar(
          flexibleSpace: SafeArea(
        child: _fnCreateAppBarWidgets(),
      )),
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: lwDisplay,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _tfDataHasChanged ? _fnUpdateData : null,
        tooltip: 'Update',
        backgroundColor: _tfDataHasChanged ? Colors.blue : Colors.grey,
        child: Icon(Icons.done),
      ),
    );
  }

//-------------------------------------------------------------------------------------

  _fnOnBuildComplete() {
    _tfInitialized = true;
    if (_tfSaveSelection && _iNdxWidgetChanged >= 0) {
      _lwTextFields[_iNdxWidgetChanged].controller.selection = _wTextSelection;
    }
  }

//-------------------------------------------------------------------------------------

  void _fnCreateAllWidgets() {
    _lwTextFields = List(_lsNewData.length);
    for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
      _fnCreateTextField(iNdxWidget);
    }
  }

//-------------------------------------------------------------------------------------

  void _fnCreateTextField(int iNdxWidget) {
    TextEditingController wController = TextEditingController();

    FocusNode wFocusNode = FocusNode();
    wFocusNode.addListener(() => _fnFocusChanged());

    _lwTextFields[iNdxWidget] = TextField(
      autofocus: false, //(iNdxWidget == 0),
      autocorrect: false,
      enabled: true,
      keyboardType: TextInputType.text,
      maxLength: 25,
      controller: wController,
      focusNode: wFocusNode,
      textInputAction: TextInputAction.next /* TYPE OF ACTION KEY */,
      onSubmitted: ((v) => _fnSetNextFocus(iNdxWidget)),
      onChanged: (text) => _fnTextListener(iNdxWidget, text),
      decoration: _fnCreateInputDecoration(
          'Text Field Number ${iNdxWidget + 1}', 'Enter Data'),
      style: _fnCreateWidgetTextStyle(Colors.blue[700]),
    );
  }

//-------------------------------------------------------------------------------------

  _fnTextListener(int iNdxWidget, String sText) {
    if (_tfInitialized) {
      _lsNewData[iNdxWidget] = sText;
      _fnCheckIfDataHasChanged(
          iNdxWidget) /* ENABLE OR DISABLE SUBMIT BUTTON */;
    }
  }

//-------------------------------------------------------------------------------------

  _fnSetNextFocus(int iNdxWidget) {
    if (_lwTextFields[iNdxWidget].focusNode.hasFocus) {
      _lwTextFields[iNdxWidget].focusNode.unfocus();
      if (iNdxWidget + 1 < _lwTextFields.length) {
        _lwTextFields[iNdxWidget + 1]?.focusNode?.requestFocus();
      }
    }
  }

//-------------------------------------------------------------------------------------

  InputDecoration _fnCreateInputDecoration(String sHeading, String sHint) {
    return InputDecoration(
      labelText: sHeading,
      hintText: sHint,
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0)),
    );
  }

//-------------------------------------------------------------------------------------

  TextStyle _fnCreateWidgetTextStyle(Color color) {
    return TextStyle(
      fontSize: 14.0,
      color: color,
    );
  }

//-------------------------------------------------------------------------------------

  List<Widget> _fnCreateDisplay() {
    List<Widget> lwDisplay = List((_lwTextFields.length * 2) + 2);
    lwDisplay[0] = SizedBox(height: 10);
    int iNdxDisplay = 1;
    for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
      _lwTextFields[iNdxWidget].controller.text = _lsNewData[iNdxWidget];
      lwDisplay[iNdxDisplay++] = _lwTextFields[iNdxWidget];
      lwDisplay[iNdxDisplay++] =
          SizedBox(height: iNdxDisplay < lwDisplay.length - 2 ? 10 : 80);
    }
    lwDisplay[lwDisplay.length - 1] = Divider(color: Colors.black, height: 2);
    return lwDisplay;
  }

//-------------------------------------------------------------------------------------

  _fnUpdateData() {
    for (int iNdxWidget = 0; iNdxWidget < _lsNewData.length; iNdxWidget++) {
      if (_lsNewData[iNdxWidget] != _lsOldData[iNdxWidget]) {
        _lsOldData[iNdxWidget] = _lsNewData[iNdxWidget];
      }
    }
    _fnCheckIfDataHasChanged(-1);
  }

//-------------------------------------------------------------------------------------

  _fnCheckIfDataHasChanged(int iNdxWidgetChanged) {
    bool tfChanged = false /* INIT */;
    for (int iNdxWidgetTest = 0;
        !tfChanged && iNdxWidgetTest < _lsNewData.length;
        iNdxWidgetTest++) {
      tfChanged = _lsNewData[iNdxWidgetTest] != _lsOldData[iNdxWidgetTest];
    }
    if (iNdxWidgetChanged >= 0) {
      _iNdxWidgetChanged = iNdxWidgetChanged;
      _wTextSelection = _lwTextFields[iNdxWidgetChanged].controller.selection;
    }
    if (tfChanged != _tfDataHasChanged) {
      setState(() => _tfDataHasChanged = tfChanged) /* WE NEED TO ENABLE FAB */;
    }
  }

//-------------------------------------------------------------------------------------

  Row _fnCreateAppBarWidgets() {
    IconData wIconData =
        _tfSaveSelection ? Icons.check_box : Icons.check_box_outline_blank;
    Color wColor = _tfSaveSelection ? Colors.blue[900] : Colors.grey[600];
    IconButton wIconButton = IconButton(
        icon: Icon(wIconData),
        color: wColor,
        onPressed: _fnCheckboxChanged,
        iconSize: 40);
    return Row(children: <Widget>[
      SizedBox(width: 10),
      Text('Save\nSelection', textAlign: TextAlign.center),
      wIconButton,
      SizedBox(width: 30),
      Text('Test TextField')
    ]);
  }

//-------------------------------------------------------------------------------------

  _fnFocusChanged() {
    for (int iNdxWidget = 0; iNdxWidget < _lwTextFields.length; iNdxWidget++) {
      if (_lwTextFields[iNdxWidget].focusNode.hasFocus) {
        _iNdxWidgetChanged = iNdxWidget;
        _wTextSelection = _lwTextFields[iNdxWidget].controller.selection;
        return;
      }
    }
  }

//-------------------------------------------------------------------------------------

  void _fnCheckboxChanged() {
    _tfSaveSelection = !_tfSaveSelection;
    if (!_tfSaveSelection) {
      _iNdxWidgetChanged = -1;
    }
    setState(() {});
  }
}


--- ----- Добавили ключ к TextField, но проблема сохраняется ---------

  key: ValueKey<int>(iNdxWidget),

Ответы [ 2 ]

0 голосов
/ 26 февраля 2020

Моя ошибка - как отправлено @ pskink

Мое оправдание - я обычно использую FAB с сохранением состояния, поэтому обычно с этим не сталкиваюсь.

Ответ: измените эту строку:

TextEditingController wController = TextEditingController(text: _lsNewData[iNdxWidget]);

и удалите это

_lwTextFields[iNdxWidget].controller.text = _lsNewData[iNdxWidget];

- pskink 23 февраля в 7: 33

0 голосов
/ 20 февраля 2020

Я надеюсь, что эти функции могут помочь вам

  void updateText(String text) {
    if (text != null) {
      this.text = _applyMask(mask, text);
    } else {
      this.text = '';
    }

    _lastUpdatedText = this.text;
  }

  void updateMask(String mask, {bool moveCursorToEnd = true}) {
    this.mask = mask;
    updateText(text);

    if (moveCursorToEnd) {
      this.moveCursorToEnd();
    }
  }

  void moveCursorToEnd() {
    final String text = _lastUpdatedText;
    selection =
        TextSelection.fromPosition(TextPosition(offset: (text ?? '').length));
  }
...