| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'input_decorator.dart'; |
| import 'text_field.dart'; |
| import 'theme.dart'; |
| |
| /// A [FormField] that contains a [TextField]. |
| /// |
| /// This is a convenience widget that wraps a [TextField] widget in a |
| /// [FormField]. |
| /// |
| /// A [Form] ancestor is not required. The [Form] simply makes it easier to |
| /// save, reset, or validate multiple fields at once. To use without a [Form], |
| /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to |
| /// save or reset the form field. |
| /// |
| /// When a [controller] is specified, its [TextEditingController.text] |
| /// defines the [initialValue]. If this [FormField] is part of a scrolling |
| /// container that lazily constructs its children, like a [ListView] or a |
| /// [CustomScrollView], then a [controller] should be specified. |
| /// The controller's lifetime should be managed by a stateful widget ancestor |
| /// of the scrolling container. |
| /// |
| /// If a [controller] is not specified, [initialValue] can be used to give |
| /// the automatically generated controller an initial value. |
| /// |
| /// For a documentation about the various parameters, see [TextField]. |
| /// |
| /// See also: |
| /// |
| /// * <https://material.google.com/components/text-fields.html> |
| /// * [TextField], which is the underlying text field without the [Form] |
| /// integration. |
| /// * [InputDecorator], which shows the labels and other visual elements that |
| /// surround the actual text editing widget. |
| /// |
| /// {@tool sample} |
| /// |
| /// Creates a [TextFormField] with an [InputDecoration] and validator function. |
| /// |
| /// ```dart |
| /// TextFormField( |
| /// decoration: const InputDecoration( |
| /// icon: Icon(Icons.person), |
| /// hintText: 'What do people call you?', |
| /// labelText: 'Name *', |
| /// ), |
| /// onSaved: (String value) { |
| /// // This optional block of code can be used to run |
| /// // code when the user saves the form. |
| /// }, |
| /// validator: (String value) { |
| /// return value.contains('@') ? 'Do not use the @ char.' : null; |
| /// }, |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| class TextFormField extends FormField<String> { |
| /// Creates a [FormField] that contains a [TextField]. |
| /// |
| /// When a [controller] is specified, [initialValue] must be null (the |
| /// default). If [controller] is null, then a [TextEditingController] |
| /// will be constructed automatically and its `text` will be initialized |
| /// to [initalValue] or the empty string. |
| /// |
| /// For documentation about the various parameters, see the [TextField] class |
| /// and [new TextField], the constructor. |
| TextFormField({ |
| Key key, |
| this.controller, |
| String initialValue, |
| FocusNode focusNode, |
| InputDecoration decoration = const InputDecoration(), |
| TextInputType keyboardType, |
| TextCapitalization textCapitalization = TextCapitalization.none, |
| TextInputAction textInputAction, |
| TextStyle style, |
| TextDirection textDirection, |
| TextAlign textAlign = TextAlign.start, |
| bool autofocus = false, |
| bool obscureText = false, |
| bool autocorrect = true, |
| bool autovalidate = false, |
| bool maxLengthEnforced = true, |
| int maxLines = 1, |
| int maxLength, |
| VoidCallback onEditingComplete, |
| ValueChanged<String> onFieldSubmitted, |
| FormFieldSetter<String> onSaved, |
| FormFieldValidator<String> validator, |
| List<TextInputFormatter> inputFormatters, |
| bool enabled = true, |
| double cursorWidth = 2.0, |
| Radius cursorRadius, |
| Color cursorColor, |
| Brightness keyboardAppearance, |
| EdgeInsets scrollPadding = const EdgeInsets.all(20.0), |
| bool enableInteractiveSelection = true, |
| }) : assert(initialValue == null || controller == null), |
| assert(textAlign != null), |
| assert(autofocus != null), |
| assert(obscureText != null), |
| assert(autocorrect != null), |
| assert(autovalidate != null), |
| assert(maxLengthEnforced != null), |
| assert(scrollPadding != null), |
| assert(maxLines == null || maxLines > 0), |
| assert(maxLength == null || maxLength > 0), |
| assert(enableInteractiveSelection != null), |
| super( |
| key: key, |
| initialValue: controller != null ? controller.text : (initialValue ?? ''), |
| onSaved: onSaved, |
| validator: validator, |
| autovalidate: autovalidate, |
| enabled: enabled, |
| builder: (FormFieldState<String> field) { |
| final _TextFormFieldState state = field; |
| final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration()) |
| .applyDefaults(Theme.of(field.context).inputDecorationTheme); |
| return TextField( |
| controller: state._effectiveController, |
| focusNode: focusNode, |
| decoration: effectiveDecoration.copyWith(errorText: field.errorText), |
| keyboardType: keyboardType, |
| textInputAction: textInputAction, |
| style: style, |
| textAlign: textAlign, |
| textDirection: textDirection, |
| textCapitalization: textCapitalization, |
| autofocus: autofocus, |
| obscureText: obscureText, |
| autocorrect: autocorrect, |
| maxLengthEnforced: maxLengthEnforced, |
| maxLines: maxLines, |
| maxLength: maxLength, |
| onChanged: field.didChange, |
| onEditingComplete: onEditingComplete, |
| onSubmitted: onFieldSubmitted, |
| inputFormatters: inputFormatters, |
| enabled: enabled, |
| cursorWidth: cursorWidth, |
| cursorRadius: cursorRadius, |
| cursorColor: cursorColor, |
| scrollPadding: scrollPadding, |
| keyboardAppearance: keyboardAppearance, |
| enableInteractiveSelection: enableInteractiveSelection, |
| ); |
| }, |
| ); |
| |
| /// Controls the text being edited. |
| /// |
| /// If null, this widget will create its own [TextEditingController] and |
| /// initialize its [TextEditingController.text] with [initialValue]. |
| final TextEditingController controller; |
| |
| @override |
| _TextFormFieldState createState() => _TextFormFieldState(); |
| } |
| |
| class _TextFormFieldState extends FormFieldState<String> { |
| TextEditingController _controller; |
| |
| TextEditingController get _effectiveController => widget.controller ?? _controller; |
| |
| @override |
| TextFormField get widget => super.widget; |
| |
| @override |
| void initState() { |
| super.initState(); |
| if (widget.controller == null) { |
| _controller = TextEditingController(text: widget.initialValue); |
| } else { |
| widget.controller.addListener(_handleControllerChanged); |
| } |
| } |
| |
| @override |
| void didUpdateWidget(TextFormField oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (widget.controller != oldWidget.controller) { |
| oldWidget.controller?.removeListener(_handleControllerChanged); |
| widget.controller?.addListener(_handleControllerChanged); |
| |
| if (oldWidget.controller != null && widget.controller == null) |
| _controller = TextEditingController.fromValue(oldWidget.controller.value); |
| if (widget.controller != null) { |
| setValue(widget.controller.text); |
| if (oldWidget.controller == null) |
| _controller = null; |
| } |
| } |
| } |
| |
| @override |
| void dispose() { |
| widget.controller?.removeListener(_handleControllerChanged); |
| super.dispose(); |
| } |
| |
| @override |
| void reset() { |
| super.reset(); |
| setState(() { |
| _effectiveController.text = widget.initialValue; |
| }); |
| } |
| |
| void _handleControllerChanged() { |
| // Suppress changes that originated from within this class. |
| // |
| // In the case where a controller has been passed in to this widget, we |
| // register this change listener. In these cases, we'll also receive change |
| // notifications for changes originating from within this class -- for |
| // example, the reset() method. In such cases, the FormField value will |
| // already have been set. |
| if (_effectiveController.text != value) |
| didChange(_effectiveController.text); |
| } |
| } |