Flutter TextField TextFieldController setState - 光标位置改变

Flutter TextField TextFieldController setState - position of cursor changes

我最近写了一个我需要的测试程序,它本质上是一个 CRUD 程序。我需要以不同于我编写的其他类似程序的方式来处理这个问题,因为我通常使用有状态的 FAB 小部件,并且不必通过 setState() 来启用和禁用 FAB。在这个测试程序中我不想使用自定义的FAB,而是使用了标准的FAB。我发现每当我因为更改 TextField 而不得不启用或禁用 FAB 时,这需要一个 setState(),并且在构建之后,正在编辑的 TextField 的光标已重新定位。我不知道为什么会这样,因为我没有重新创建小部件。我能想出的解决该问题的唯一解决方案相当混乱,需要保存 Widget 在 TextField 列表中的位置并保存 Selection,然后在构建后将 Selection 重置为保存的 Selection。

我需要实现的是仅在数据更改时启用 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),

希望这些功能对您有所帮助

  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));
  }

我的错误 - 由@pskink 发布

我的借口 - 我通常使用有状态的 FAB,所以我通常不会遇到这种情况。

回答: 所以改变这一行:

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

并删除这个

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

– pskink 2 月 23 日在 7:33