Flutter 使用按键显示日期选择器

Flutter show datepicker with keypress

这是我的第一个问题,如果我不清楚我所写的内容,我深表歉意(我是法国人,不会用我的英语..)

所以我是 Flutter 和开发世界的新手,我从事一个显示表单的项目。这里我们有一个输入 who onTap return 一个 DateTime 到 select 一个日期。没问题。但是输入不接受焦点,所以我们添加 FocusNode。我修改代码以添加对焦点输入的视觉反馈。但是如果不点击输入就无法打开日期选择器。我们想在 space 时打开它或在按下时输入并保留现有的方式。

所以我有一个用于这些键盘键的方法 KeyEventResult。但是我不知道如何在我们按下日期选择器时 return 以及如何 return 到页面。当我们按下这些键以显示日期选择器时,我尝试设置一个布尔值,但构建函数 returned 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 方法中使用它。

另外我在调用新方法的时候调整了另外一个问题

其实很简单...

我post代码如果它可以对某人有帮助:)

//#################################################################################################
/// 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,
    );
  }
}