这是我的第一个问题,如果我不清楚我写的是什么,我很抱歉(我是法国人,不使用我的英语…)
所以我是新手在Flutter和World of Dev,我在一个项目谁显示表单工作。这里我们有一个输入谁onTap返回一个DateTime来选择一个日期。没有问题。但是输入不接受焦点,所以我们添加FocusNode。我修改了代码,在焦点输入上添加了视觉反馈。但是如果不点击输入,就不可能打开日期选择器。我们希望在按空格或回车键时打开它,并保持现有的方式。
我有一个方法KeyEventResult用于这些键盘键。但我不知道如何返回日期选择器,当我们按下他们,并返回到页面。当我们按下这些键来显示日期选择器时,我尝试设置一个bool,但构建函数返回null,按下tab后显示日期选择器,但我不能离开它。我不知道我到底需要怎么用它。
我向您展示了我拥有的初始代码和我最后修改的第二个代码。我希望你的帮助,我认为这并不难,但我错过了能力....谢谢你的帮助!
首次实现焦点的初始代码
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{@required this.properties,
@required this.lines,
@required this.templateCell,
@required this.formField,
@required this.fieldName,
@required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
@override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
@override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
@override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
void _onFocusChanges() {}
//-----------------------------------------------------------------------------------------------
@override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
@override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(d{2}/d{2}/d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: () async {
if (!isReadonly) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (pickedDate == null) {
setState(() {
_dateController.text = '';
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
});
}
}
},
),
),
),
alerts,
validationMessages,
);
}
}
我modif '
import 'package:afi_dto/template/t_cell.dart';
import 'package:afi_flutter_client/config/i18n/i18n.dart';
import 'package:afi_flutter_client/models/form/form_field.dart' as modelFormField;
import 'package:afi_flutter_client/services/providers/form_provider.dart';
import 'package:afi_flutter_client/ui/forms/input/element_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_translate/global.dart';
import 'package:provider/provider.dart';
import 'package:reactive_forms/reactive_forms.dart';
//#################################################################################################
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{@required this.properties,
@required this.lines,
@required this.templateCell,
@required this.formField,
@required this.fieldName,
@required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
@override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
bool _focused = false;
bool _setDatePicker = false;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
@override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context, onKey: _handleKeyPress);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
@override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
Future<Null> _onFocusChanges() async {
if (_fieldFocusNode != _focused) {
setState(() {
_focused = _fieldFocusNode.hasFocus;
});
}
}
//-----------------------------------------------------------------------------------------------
KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.space || event.logicalKey == LogicalKeyboardKey.enter) {
setState(() {
_setDatePicker = true;
});
print("key pressed: $event.logicalKey");
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
//-----------------------------------------------------------------------------------------------
@override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
@override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(d{2}/d{2}/d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
//-----------------------------------------------------------------------------------------------
Future _datePicker() async {
if (!isReadonly) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (pickedDate == null) {
setState(() {
_dateController.text = '';
_focused = true;
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
_focused = true;
});
}
}
}
//-----------------------------------------------------------------------------------------------
if ( _setDatePicker) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
} else {
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
isFocused: _focused,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: _datePicker ,
),
),
),
alerts,
validationMessages,
);
}
}
}
所以我解决了我的问题。但我不知道如何编辑我的第一个消息或关闭它。
我只是提取onTap中的方法来创建另一个方法,并在onTap和_handleKeyPress方法中使用它。
在调用新方法时,我还调整了另一个问题。
其实很简单…
我张贴的代码,如果它可以帮助别人:)
//#################################################################################################
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{@required this.properties,
@required this.lines,
@required this.templateCell,
@required this.formField,
@required this.fieldName,
@required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
@override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
bool _focused = false;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
@override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context, onKey: _handleKeyPress);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
@override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
Future<Null> _onFocusChanges() async {
if (_fieldFocusNode.hasFocus != _focused) {
setState(() {
_focused = _fieldFocusNode.hasFocus;
});
}
}
//-----------------------------------------------------------------------------------------------
void activeDatePicker() async {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (_focused) {
_fieldFocusNode.unfocus();
} else {
_fieldFocusNode.requestFocus();
}
if (pickedDate == null) {
setState(() {
_dateController.text = '';
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
});
}
}
//-----------------------------------------------------------------------------------------------
KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.space || event.logicalKey == LogicalKeyboardKey.enter) {
activeDatePicker();
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
//-----------------------------------------------------------------------------------------------
@override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
@override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(d{2}/d{2}/d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
//-----------------------------------------------------------------------------------------------
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
isFocused: _focused,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: () {
if (!isReadonly) {
activeDatePicker();
}
} ,
),
),
),
alerts,
validationMessages,
);
}
}