#flutter
#flutter
Вопрос:
Мы заняты созданием мобильного приложения в Flutter, которое относится к кредитным картам. В нашем дизайне мы планировали использовать числовые поля для карты в виде VIN-номера и даты истечения срока действия, используя 4 отдельных поля, как показано на рисунке ниже (я уверен, что вы видели аналогичные реализации в других приложениях).:
Здесь важно то, что эти 4 поля по-прежнему должны действовать как единое поле, а именно, если вы вводите, оно должно перейти к следующему, если вы удаляете или нажимаете влево, оно должно перейти к предыдущему.
Мы попробовали два разных подхода, которые оба вызывают у нас проблемы:
- Используйте четыре разных поля — это работало довольно хорошо, но очень быстро глючило, когда пользователь удалял или пытался перемещаться между числами.
- Используйте одно поле ввода с расширенными буквами, разделяющими четыре строки, нарисованные внизу — это снова работает неплохо, но поскольку шрифт, который мы используем, не моноширинный (буквы и цифры одинакового размера), цифры на самом деле не совпадают должным образом со строками внизу, что делает его довольно ужасным.
Я провел некоторые поиски, но до сих пор не нашел никого, кто сделал что-то подобное и опубликовал об этом в Интернете. Довольно сложно заставить 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 в этой ссылке, в частности, перечислены несколько пакетов, которые должны работать для меня