flutter - 颤振显示日期选择器与按键
问题描述
这是我的第一个问题,如果我不清楚我写的是什么,我很抱歉(我是法国人,不会用我的英语..)
所以我是 Flutter 和 World of Dev 的新手,我从事一个显示表单的项目。这里我们有一个输入,他 onTap 返回一个 DateTime 来选择一个日期。没问题。但是输入不接受焦点,所以我们添加 FocusNode。我修改代码以在焦点输入上添加视觉反馈。但是如果不点击输入,就无法打开日期选择器。我们希望在空格时打开它或在按下时输入并保留现有方式。
所以我有一个方法 KeyEventResult 用于这些键盘键。但是当我们按下日期选择器并返回页面时,我不知道如何返回它们。当我们按下这些键以显示日期选择器时,我尝试设置一个布尔值,但构建函数返回 null 并且在按下选项卡后显示日期选择器,但我无法离开它。我不知道我真的需要如何使用它..
我向您展示了我拥有的初始代码以及我最后修改的第二个代码。希望您的帮助,我认为这并不难,但我想念能力...。感谢您的帮助!
首次实现焦点的初始代码
/// 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,
);
}
}
我的修改
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,
);
}
}
推荐阅读
- naming - 验证 vs 验证 vs 评估
- java - 不预先定义数组大小的数组输入
- wordpress-theming - WordPress覆盖隐藏模板
- javascript - 如何解决错误“this.props.myFunction”不是反应中的函数?
- mongodb - 带有 graphql 的 mongoose.populate() 无法执行“无限”查询
- typescript - ion-datetime - 从出生日期计算年龄
- ios - 如何在 Facebook 嵌入式视频中为 WKWebView 在 Objective-C 中设置“playsinline = 1”
- java - 未考虑 Maven 依赖项管理
- ios - 如何将简单(非主题)UTF-8 图标放在屏幕上?
- deployment - 为什么滚动更新部署甚至缩小副本集时会出现停机时间