Подключение нескольких текстовых полей для работы как одно поле в Flutter

#flutter

#flutter

Вопрос:

Мы заняты созданием мобильного приложения в Flutter, которое относится к кредитным картам. В нашем дизайне мы планировали использовать числовые поля для карты в виде VIN-номера и даты истечения срока действия, используя 4 отдельных поля, как показано на рисунке ниже (я уверен, что вы видели аналогичные реализации в других приложениях).:

введите описание изображения здесь

Здесь важно то, что эти 4 поля по-прежнему должны действовать как единое поле, а именно, если вы вводите, оно должно перейти к следующему, если вы удаляете или нажимаете влево, оно должно перейти к предыдущему.

Мы попробовали два разных подхода, которые оба вызывают у нас проблемы:

  1. Используйте четыре разных поля — это работало довольно хорошо, но очень быстро глючило, когда пользователь удалял или пытался перемещаться между числами.
  2. Используйте одно поле ввода с расширенными буквами, разделяющими четыре строки, нарисованные внизу — это снова работает неплохо, но поскольку шрифт, который мы используем, не моноширинный (буквы и цифры одинакового размера), цифры на самом деле не совпадают должным образом со строками внизу, что делает его довольно ужасным.

Я провел некоторые поиски, но до сих пор не нашел никого, кто сделал что-то подобное и опубликовал об этом в Интернете. Довольно сложно заставить Google понять, о чем я спрашиваю, что также усложняет поиск решения.

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

PS:

Хотя это, но запутанный, пожалуйста, смотрите наш код с несколькими полями ввода ниже:

Несколько текстовых полей:

 import 'package:creditcardcurator/widgets/textFields/single_number_textfield.dart';
import 'package:flutter/material.dart';
import 'package:print_color/print_color.dart';

var focusNodes;

bool getFirstFieldFocus = true;

class ExpiryDateTextField extends StatefulWidget {
  final List<TextEditingController> controllers;
  final Function onChangeF;
  final Function onComplete;
  final bool startFocus;
  final FocusNode focusNode;
  final bool isDisbaled;
  void requestFocus() {
    Print.red(getFirstFieldFocus.toString());
    if (getFirstFieldFocus) focusNodes[0].requestFocus();
  }

  ExpiryDateTextField(
      {this.controllers,
      this.onChangeF,
      this.onComplete,
      this.startFocus = false,
      this.focusNode,
      this.isDisbaled = false});

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

class _ExpiryDateTextFieldState extends State<ExpiryDateTextField> {
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    focusNodes = [
      widget.focusNode,
      FocusNode(),
      FocusNode(),
      FocusNode(),
    ];

    getFirstFieldFocus = true;
    if (widget.startFocus) widget.requestFocus();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.startFocus) widget.requestFocus();

    bool onSwitchChanged(value, context, index) {
      getFirstFieldFocus = false;
      if (index == 4) {
        //if it is on the last text box, do nothing
      } else {
        if (widget.controllers[index].text.length > 0) {
          index  ;
          FocusScope.of(context).requestFocus(focusNodes[index]);
          widget.controllers[index].selection = TextSelection(
              baseOffset: 0,
              extentOffset: widget.controllers[index].text.length);
          // FocusScope.of(context).focus
        }
      }
      return true;
    }

    return Row(
      children: <Widget>[
        Expanded(
          flex: 1,
          child: SingleDigitTextField(
              isDisbaled: widget.isDisbaled,
              onChangedF: (value) {
                widget.onChangeF(value, 0);
                // print(this.toString(minLevel: DiagnosticLevel.debug));
                onSwitchChanged(value, context, 0);
              },
              fNode: focusNodes[0],
              cController: widget.controllers[0]),
        ),
        Expanded(
          flex: 1,
          child: SingleDigitTextField(
              isDisbaled: widget.isDisbaled,
              onChangedF: (value) {
                widget.onChangeF(value, 1);
                onSwitchChanged(value, context, 1);
              },
              fNode: focusNodes[1],
              cController: widget.controllers[1]),
        ),
        Expanded(
          flex: 1,
          child: Text("/",
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.display2),
        ),
        Expanded(
          flex: 1,
          child: SingleDigitTextField(
              isDisbaled: widget.isDisbaled,
              onChangedF: (value) {
                widget.onChangeF(value, 2);
                onSwitchChanged(value, context, 2);
              },
              fNode: focusNodes[2],
              cController: widget.controllers[2]),
        ),
        Expanded(
          flex: 1,
          child: SingleDigitTextField(
            isDisbaled: widget.isDisbaled,
            onChangedF: (value) {
              widget.onChangeF(value, 3);
              if (value != null amp;amp; value.length != 0) {
                try {
                  widget.onComplete();
                } catch (e) {
                  print(e.toString());
                }
              }
              onSwitchChanged(value, context, 3);
            },
            fNode: focusNodes[3],
            cController: widget.controllers[3],
            onComplete: widget.onComplete,
          ),
        ),
      ],
    );
  }
}
 

Одно текстовое поле:

 import 'package:creditcardcurator/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class SingleDigitTextField extends StatelessWidget {
  final Function onChangedF;
  final FocusNode fNode;
  final TextEditingController cController;
  final Function onComplete;
  final bool isDisbaled;
  // final Widget child;
  // DollarTextField({this.child});
  SingleDigitTextField(
      {this.onChangedF,
      this.fNode,
      this.cController,
      this.onComplete,
      this.isDisbaled = false});

  void defaultFunction() {
    print("default function actioned");
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      // margin: const EdgeInsets.all(8.0),
      padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 0.0),
      width: 50,
      child: TextField(
        enabled: !isDisbaled,
        inputFormatters: [
          WhitelistingTextInputFormatter(new RegExp('[0-9,.]'))
        ],
        controller: cController,
        onTap: () {
          // this.cController.selection = TextSelection.fromPosition(
          //     TextPosition(offset: this.cController.text.length)); //cursor to end of textfield
          cController.selection = TextSelection(
              baseOffset: 0,
              extentOffset: cController.text.length); //to begining of textfield
        },
        focusNode: fNode,
        textAlign: TextAlign.center,
        style: TextStyle(
          fontSize: getd3FontSize(context),
          fontFamily: getFontFamily(context),
          color: Colors.white,
        ),
        maxLength: 1,
        decoration: InputDecoration(
          contentPadding: EdgeInsets.only(bottom: -20),
          counter: null,
          helperStyle: TextStyle(
            color: Colors.transparent,
          ), //hiding the max length hint text
          enabledBorder: UnderlineInputBorder(
            borderSide: BorderSide(color: Colors.white),
          ),
        ),
        keyboardType:
            TextInputType.numberWithOptions(signed: true, decimal: true),
        // keyboardType: TextInputType.number,
        onChanged: (e) {
          onChangedF(e);
        },
        onEditingComplete: () {
          try {
            onComplete();
          } catch (e) {
            print(e.toString());
          }
        },
      ),
    );
  }
}
 

Комментарии:

1. использование максимальной длины для каждого поля и правильное использование focusnode может дать вам функциональность, которую вы ищете.

2. может быть, попробуйте заглянуть в исходный код какого-нибудь пакета pin_code и изменить его в соответствии с вашим дизайном

3. @DungNgo большое вам спасибо, это действительно дало мне что-то полезное для поиска! Я нашел пару пакетов, которые будут работать идеально!

4. fluttergems.dev/pin-password-field в этой ссылке, в частности, перечислены несколько пакетов, которые должны работать для меня