From a24bfed0e7b65f4a927af2ddc8ff7440a2051e24 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 15 Aug 2019 12:30:19 -0700 Subject: [PATCH] Add autofocus parameter to widgets which use Focus widget internally (#37809) Add an autofocus parameter to widgets which use Focus widget internally, and update related docs. This will allow developers to request that a particular widget be automatically focused when shown. --- .../flutter/lib/src/cupertino/text_field.dart | 13 ++- packages/flutter/lib/src/material/button.dart | 19 ++-- packages/flutter/lib/src/material/chip.dart | 94 +++++++++++++++---- .../flutter/lib/src/material/flat_button.dart | 12 ++- .../src/material/floating_action_button.dart | 21 +++-- .../flutter/lib/src/material/icon_button.dart | 17 ++-- .../lib/src/material/material_button.dart | 24 +++-- .../lib/src/material/outline_button.dart | 14 ++- .../lib/src/material/raised_button.dart | 17 +++- .../lib/src/material/selectable_text.dart | 7 +- .../lib/src/material/toggle_buttons.dart | 8 +- .../lib/src/widgets/editable_text.dart | 9 +- .../flutter/lib/src/widgets/focus_scope.dart | 27 ++++-- .../src/widgets/raw_keyboard_listener.dart | 21 ++++- 14 files changed, 220 insertions(+), 83 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index c94a1a446e..79081a1353 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -160,7 +160,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe /// Design UI conventions. /// * [EditableText], which is the raw text editing control at the heart of a /// [TextField]. -/// * Learn how to use a [TextEditingController] in one of our [cookbook recipe]s.(https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller) +/// * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller). class CupertinoTextField extends StatefulWidget { /// Creates an iOS-style text field. /// @@ -179,6 +179,13 @@ class CupertinoTextField extends StatefulWidget { /// The text cursor is not shown if [showCursor] is false or if [showCursor] /// is null (the default) and [readOnly] is true. /// + /// If specified, the [maxLength] property must be greater than zero. + /// + /// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior], + /// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly], + /// [scrollPadding], [suffixMode], and [textAlign] properties must not be + /// null. + /// /// See also: /// /// * [minLines] @@ -276,9 +283,7 @@ class CupertinoTextField extends StatefulWidget { /// If null, this widget will create its own [TextEditingController]. final TextEditingController controller; - /// Controls whether this widget has keyboard focus. - /// - /// If null, this widget will create its own [FocusNode]. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; /// Controls the [BoxDecoration] of the box behind the text input. diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index b9a15b25bf..95e03c5466 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -31,9 +31,9 @@ class RawMaterialButton extends StatefulWidget { /// Create a button based on [Semantics], [Material], and [InkWell] widgets. /// /// The [shape], [elevation], [focusElevation], [hoverElevation], - /// [highlightElevation], [disabledElevation], [padding], [constraints], and - /// [clipBehavior] arguments must not be null. Additionally, [elevation], - /// [focusElevation], [hoverElevation], [highlightElevation], and + /// [highlightElevation], [disabledElevation], [padding], [constraints], + /// [autofocus], and [clipBehavior] arguments must not be null. Additionally, + /// [elevation], [focusElevation], [hoverElevation], [highlightElevation], and /// [disabledElevation] must be non-negative. const RawMaterialButton({ Key key, @@ -56,6 +56,7 @@ class RawMaterialButton extends StatefulWidget { this.animationDuration = kThemeChangeDuration, this.clipBehavior = Clip.none, this.focusNode, + this.autofocus = false, MaterialTapTargetSize materialTapTargetSize, this.child, }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded, @@ -69,6 +70,7 @@ class RawMaterialButton extends StatefulWidget { assert(constraints != null), assert(animationDuration != null), assert(clipBehavior != null), + assert(autofocus != null), super(key: key); /// Called when the button is tapped or otherwise activated. @@ -232,14 +234,12 @@ class RawMaterialButton extends StatefulWidget { /// * [MaterialTapTargetSize], for a description of how this affects tap targets. final MaterialTapTargetSize materialTapTargetSize; - /// An optional focus node to use for requesting focus when pressed. - /// - /// If not supplied, the button will create and host its own [FocusNode]. - /// - /// If supplied, the given focusNode will be _hosted_ by this widget. See - /// [FocusNode] for more information on what that implies. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// {@macro flutter.widgets.Clip} final Clip clipBehavior; @@ -331,6 +331,7 @@ class _RawMaterialButtonState extends State { final Widget result = Focus( focusNode: widget.focusNode, onFocusChange: _handleFocusedChanged, + autofocus: widget.autofocus, child: ConstrainedBox( constraints: widget.constraints, child: Material( diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 35b901c4c2..4cefe3ae35 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -98,6 +98,12 @@ abstract class ChipAttributes { /// {@macro flutter.widgets.Clip} Clip get clipBehavior; + /// {@macro flutter.widgets.Focus.focusNode} + FocusNode get focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + bool get autofocus; + /// Color to be used for the unselected, enabled chip's background. /// /// The default is light grey. @@ -493,7 +499,7 @@ abstract class TappableChipAttributes { class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttributes { /// Creates a material design chip. /// - /// The [label] and [clipBehavior] arguments must not be null. + /// The [label], [autofocus], and [clipBehavior] arguments must not be null. /// The [elevation] must be null or non-negative. const Chip({ Key key, @@ -507,12 +513,15 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri this.deleteButtonTooltipMessage, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.padding, this.materialTapTargetSize, this.elevation, this.shadowColor, }) : assert(label != null), + assert(autofocus != null), assert(clipBehavior != null), assert(elevation == null || elevation >= 0.0), super(key: key); @@ -530,6 +539,10 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -563,6 +576,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri tapEnabled: false, shape: shape, clipBehavior: clipBehavior, + focusNode: focusNode, + autofocus: autofocus, backgroundColor: backgroundColor, padding: padding, materialTapTargetSize: materialTapTargetSize, @@ -630,9 +645,10 @@ class InputChip extends StatelessWidget /// The [onPressed] and [onSelected] callbacks must not both be specified at /// the same time. /// - /// The [label], [isEnabled], [selected], and [clipBehavior] arguments must - /// not be null. The [pressElevation] and [elevation] must be null or - /// non-negative. Typically, [pressElevation] is greater than [elevation]. + /// The [label], [isEnabled], [selected], [autofocus], and [clipBehavior] + /// arguments must not be null. The [pressElevation] and [elevation] must be + /// null or non-negative. Typically, [pressElevation] is greater than + /// [elevation]. const InputChip({ Key key, this.avatar, @@ -653,6 +669,8 @@ class InputChip extends StatelessWidget this.tooltip, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.padding, this.materialTapTargetSize, @@ -664,6 +682,7 @@ class InputChip extends StatelessWidget assert(isEnabled != null), assert(label != null), assert(clipBehavior != null), + assert(autofocus != null), assert(pressElevation == null || pressElevation >= 0.0), assert(elevation == null || elevation >= 0.0), super(key: key); @@ -705,6 +724,10 @@ class InputChip extends StatelessWidget @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -741,6 +764,8 @@ class InputChip extends StatelessWidget tooltip: tooltip, shape: shape, clipBehavior: clipBehavior, + focusNode: focusNode, + autofocus: autofocus, backgroundColor: backgroundColor, padding: padding, materialTapTargetSize: materialTapTargetSize, @@ -814,9 +839,9 @@ class ChoiceChip extends StatelessWidget DisabledChipAttributes { /// Create a chip that acts like a radio button. /// - /// The [label], [selected], and [clipBehavior] arguments must not be null. - /// The [pressElevation] and [elevation] must be null or non-negative. - /// Typically, [pressElevation] is greater than [elevation]. + /// The [label], [selected], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. const ChoiceChip({ Key key, this.avatar, @@ -831,6 +856,8 @@ class ChoiceChip extends StatelessWidget this.tooltip, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.padding, this.materialTapTargetSize, @@ -841,6 +868,7 @@ class ChoiceChip extends StatelessWidget }) : assert(selected != null), assert(label != null), assert(clipBehavior != null), + assert(autofocus != null), assert(pressElevation == null || pressElevation >= 0.0), assert(elevation == null || elevation >= 0.0), super(key: key); @@ -870,6 +898,10 @@ class ChoiceChip extends StatelessWidget @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -904,6 +936,8 @@ class ChoiceChip extends StatelessWidget tooltip: tooltip, shape: shape, clipBehavior: clipBehavior, + focusNode: focusNode, + autofocus: autofocus, disabledColor: disabledColor, selectedColor: selectedColor ?? chipTheme.secondarySelectedColor, backgroundColor: backgroundColor, @@ -1011,9 +1045,9 @@ class FilterChip extends StatelessWidget DisabledChipAttributes { /// Create a chip that acts like a checkbox. /// - /// The [selected], [label], and [clipBehavior] arguments must not be null. - /// The [pressElevation] and [elevation] must be null or non-negative. - /// Typically, [pressElevation] is greater than [elevation]. + /// The [selected], [label], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. const FilterChip({ Key key, this.avatar, @@ -1028,6 +1062,8 @@ class FilterChip extends StatelessWidget this.tooltip, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.padding, this.materialTapTargetSize, @@ -1038,6 +1074,7 @@ class FilterChip extends StatelessWidget }) : assert(selected != null), assert(label != null), assert(clipBehavior != null), + assert(autofocus != null), assert(pressElevation == null || pressElevation >= 0.0), assert(elevation == null || elevation >= 0.0), super(key: key); @@ -1067,6 +1104,10 @@ class FilterChip extends StatelessWidget @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -1098,6 +1139,8 @@ class FilterChip extends StatelessWidget tooltip: tooltip, shape: shape, clipBehavior: clipBehavior, + focusNode: focusNode, + autofocus: autofocus, backgroundColor: backgroundColor, disabledColor: disabledColor, selectedColor: selectedColor, @@ -1162,9 +1205,9 @@ class FilterChip extends StatelessWidget class ActionChip extends StatelessWidget implements ChipAttributes, TappableChipAttributes { /// Create a chip that acts like a button. /// - /// The [label], [onPressed] and [clipBehavior] arguments must not be null. - /// The [pressElevation] and [elevation] must be null or non-negative. - /// Typically, [pressElevation] is greater than [elevation]. + /// The [label], [onPressed], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. const ActionChip({ Key key, this.avatar, @@ -1176,12 +1219,15 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip this.tooltip, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.padding, this.materialTapTargetSize, this.elevation, this.shadowColor, }) : assert(label != null), + assert(autofocus != null), assert( onPressed != null, 'Rather than disabling an ActionChip by setting onPressed to null, ' @@ -1210,6 +1256,10 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -1233,6 +1283,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip backgroundColor: backgroundColor, shape: shape, clipBehavior: clipBehavior, + focusNode: focusNode, + autofocus: autofocus, padding: padding, labelPadding: labelPadding, isEnabled: true, @@ -1285,9 +1337,10 @@ class RawChip extends StatefulWidget /// The [onPressed] and [onSelected] callbacks must not both be specified at /// the same time. /// - /// The [label], [isEnabled], [selected], and [clipBehavior] arguments must - /// not be null. The [pressElevation] and [elevation] must be null or non-negative. - /// Typically, [pressElevation] is greater than [elevation]. + /// The [label], [isEnabled], [selected], [autofocus], and [clipBehavior] + /// arguments must not be null. The [pressElevation] and [elevation] must be + /// null or non-negative. Typically, [pressElevation] is greater than + /// [elevation]. const RawChip({ Key key, this.avatar, @@ -1311,6 +1364,8 @@ class RawChip extends StatefulWidget this.tooltip, this.shape, this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, this.backgroundColor, this.materialTapTargetSize, this.elevation, @@ -1321,6 +1376,7 @@ class RawChip extends StatefulWidget assert(isEnabled != null), assert(selected != null), assert(clipBehavior != null), + assert(autofocus != null), assert(pressElevation == null || pressElevation >= 0.0), assert(elevation == null || elevation >= 0.0), deleteIcon = deleteIcon ?? _kDefaultDeleteIcon, @@ -1363,6 +1419,10 @@ class RawChip extends StatefulWidget @override final Clip clipBehavior; @override + final FocusNode focusNode; + @override + final bool autofocus; + @override final Color backgroundColor; @override final EdgeInsetsGeometry padding; @@ -1663,6 +1723,8 @@ class _RawChipState extends State with TickerProviderStateMixin class FlatButton extends MaterialButton { /// Create a simple text button. + /// + /// The [autofocus] and [clipBehavior] arguments must not be null. const FlatButton({ Key key, @required VoidCallback onPressed, @@ -115,9 +117,11 @@ class FlatButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus = false, MaterialTapTargetSize materialTapTargetSize, @required Widget child, - }) : super( + }) : assert(autofocus != null), + super( key: key, onPressed: onPressed, onHighlightChanged: onHighlightChanged, @@ -135,6 +139,7 @@ class FlatButton extends MaterialButton { shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, child: child, ); @@ -164,6 +169,7 @@ class FlatButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus, MaterialTapTargetSize materialTapTargetSize, @required Widget icon, @required Widget label, @@ -192,6 +198,7 @@ class FlatButton extends MaterialButton { shape: buttonTheme.getShape(this), clipBehavior: clipBehavior ?? Clip.none, focusNode: focusNode, + autofocus: autofocus, materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this), animationDuration: buttonTheme.getAnimationDuration(this), child: child, @@ -222,11 +229,13 @@ class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus = false, MaterialTapTargetSize materialTapTargetSize, @required Widget icon, @required Widget label, }) : assert(icon != null), assert(label != null), + assert(autofocus != null), super( key: key, onPressed: onPressed, @@ -245,6 +254,7 @@ class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin { shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, child: Row( mainAxisSize: MainAxisSize.min, diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index 8f6b6b7186..31f32ede96 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -145,6 +145,7 @@ class FloatingActionButton extends StatelessWidget { this.shape, this.clipBehavior = Clip.none, this.focusNode, + this.autofocus = false, this.materialTapTargetSize, this.isExtended = false, }) : assert(elevation == null || elevation >= 0.0), @@ -154,15 +155,16 @@ class FloatingActionButton extends StatelessWidget { assert(disabledElevation == null || disabledElevation >= 0.0), assert(mini != null), assert(isExtended != null), + assert(autofocus != null), _sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints, super(key: key); /// Creates a wider [StadiumBorder]-shaped floating action button with /// an optional [icon] and a [label]. /// - /// The [label] and [clipBehavior] arguments must non-null. Additionally, - /// [elevation], [highlightElevation], and [disabledElevation] (if specified) - /// must be non-negative. + /// The [label], [autofocus], and [clipBehavior] arguments must non-null. + /// Additionally, [elevation], [highlightElevation], and [disabledElevation] + /// (if specified) must be non-negative. FloatingActionButton.extended({ Key key, this.tooltip, @@ -183,6 +185,7 @@ class FloatingActionButton extends StatelessWidget { this.materialTapTargetSize, this.clipBehavior = Clip.none, this.focusNode, + this.autofocus = false, Widget icon, @required Widget label, }) : assert(elevation == null || elevation >= 0.0), @@ -191,6 +194,7 @@ class FloatingActionButton extends StatelessWidget { assert(highlightElevation == null || highlightElevation >= 0.0), assert(disabledElevation == null || disabledElevation >= 0.0), assert(isExtended != null), + assert(autofocus != null), _sizeConstraints = _kExtendedSizeConstraints, mini = false, child = _ChildOverflowBox( @@ -372,14 +376,12 @@ class FloatingActionButton extends StatelessWidget { /// floating action buttons are scaled and faded in. final bool isExtended; - /// An optional focus node to use for requesting focus when pressed. - /// - /// If not supplied, the button will create and host its own [FocusNode]. - /// - /// If supplied, the given focusNode will be _hosted_ by this widget. See - /// [FocusNode] for more information on what that implies. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Configures the minimum size of the tap target. /// /// Defaults to [ThemeData.materialTapTargetSize]. @@ -463,6 +465,7 @@ class FloatingActionButton extends StatelessWidget { shape: shape, clipBehavior: clipBehavior ?? Clip.none, focusNode: focusNode, + autofocus: autofocus, child: child, ); diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 331a019f05..fa49738084 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -131,8 +131,8 @@ class IconButton extends StatelessWidget { /// /// Requires one of its ancestors to be a [Material] widget. /// - /// The [iconSize], [padding], and [alignment] arguments must not be null (though - /// they each have default values). + /// The [iconSize], [padding], [autofocus], and [alignment] arguments must not + /// be null (though they each have default values). /// /// The [icon] argument must be specified, and is typically either an [Icon] /// or an [ImageIcon]. @@ -150,10 +150,12 @@ class IconButton extends StatelessWidget { this.disabledColor, @required this.onPressed, this.focusNode, + this.autofocus = false, this.tooltip, }) : assert(iconSize != null), assert(padding != null), assert(alignment != null), + assert(autofocus != null), assert(icon != null), super(key: key); @@ -256,14 +258,12 @@ class IconButton extends StatelessWidget { /// If this is set to null, the button will be disabled. final VoidCallback onPressed; - /// An optional focus node to use for requesting focus when pressed. - /// - /// If not supplied, the button will create and host its own [FocusNode]. - /// - /// If supplied, the given focusNode will be _hosted_ by this widget. See - /// [FocusNode] for more information on what that implies. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Text that describes the action that will occur when the button is pressed. /// /// This text is displayed when the user long-presses on the button and is @@ -312,6 +312,7 @@ class IconButton extends StatelessWidget { enabled: onPressed != null, child: Focus( focusNode: focusNode, + autofocus: autofocus, child: InkResponse( onTap: onPressed, child: result, diff --git a/packages/flutter/lib/src/material/material_button.dart b/packages/flutter/lib/src/material/material_button.dart index f5151c4240..600050a070 100644 --- a/packages/flutter/lib/src/material/material_button.dart +++ b/packages/flutter/lib/src/material/material_button.dart @@ -43,6 +43,11 @@ class MaterialButton extends StatelessWidget { /// Rather than creating a material button directly, consider using /// [FlatButton] or [RaisedButton]. To create a custom Material button /// consider using [RawMaterialButton]. + /// + /// The [autofocus] and [clipBehavior] arguments must not be null. + /// Additionally, [elevation], [hoverElevation], [focusElevation], + /// [highlightElevation], and [disabledElevation] must be non-negative, if + /// specified. const MaterialButton({ Key key, @required this.onPressed, @@ -66,12 +71,19 @@ class MaterialButton extends StatelessWidget { this.shape, this.clipBehavior = Clip.none, this.focusNode, + this.autofocus = false, this.materialTapTargetSize, this.animationDuration, this.minWidth, this.height, this.child, - }) : super(key: key); + }) : assert(autofocus != null), + assert(elevation == null || elevation >= 0.0), + assert(focusElevation == null || focusElevation >= 0.0), + assert(hoverElevation == null || hoverElevation >= 0.0), + assert(highlightElevation == null || highlightElevation >= 0.0), + assert(disabledElevation == null || disabledElevation >= 0.0), + super(key: key); /// The callback that is called when the button is tapped or otherwise activated. /// @@ -296,14 +308,12 @@ class MaterialButton extends StatelessWidget { /// {@macro flutter.widgets.Clip} final Clip clipBehavior; - /// An optional focus node to use for requesting focus when pressed. - /// - /// If not supplied, the button will create and host its own [FocusNode]. - /// - /// If supplied, the given focusNode will be _hosted_ by this widget. See - /// [FocusNode] for more information on what that implies. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Defines the duration of animated changes for [shape] and [elevation]. /// /// The default value is [kThemeChangeDuration]. diff --git a/packages/flutter/lib/src/material/outline_button.dart b/packages/flutter/lib/src/material/outline_button.dart index decbed5599..fda1ab961b 100644 --- a/packages/flutter/lib/src/material/outline_button.dart +++ b/packages/flutter/lib/src/material/outline_button.dart @@ -57,7 +57,7 @@ class OutlineButton extends MaterialButton { /// Create an outline button. /// /// The [highlightElevation] argument must be null or a positive value - /// and the [clipBehavior] argument must not be null. + /// and the [autofocus] and [clipBehavior] arguments must not be null. const OutlineButton({ Key key, @required VoidCallback onPressed, @@ -77,8 +77,10 @@ class OutlineButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus = false, Widget child, }) : assert(highlightElevation == null || highlightElevation >= 0.0), + assert(autofocus != null), super( key: key, onPressed: onPressed, @@ -95,6 +97,7 @@ class OutlineButton extends MaterialButton { shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, child: child, ); @@ -105,7 +108,7 @@ class OutlineButton extends MaterialButton { /// at the start, and 16 at the end, with an 8 pixel gap in between. /// /// The [highlightElevation] argument must be null or a positive value. The - /// [icon], [label], and [clipBehavior] arguments must not be null. + /// [icon], [label], [autofocus], and [clipBehavior] arguments must not be null. factory OutlineButton.icon({ Key key, @required VoidCallback onPressed, @@ -125,6 +128,7 @@ class OutlineButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus, @required Widget icon, @required Widget label, }) = _OutlineButtonWithIcon; @@ -218,9 +222,11 @@ class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMi ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus = false, @required Widget icon, @required Widget label, }) : assert(highlightElevation == null || highlightElevation >= 0.0), + assert(autofocus != null), assert(icon != null), assert(label != null), super( @@ -242,6 +248,7 @@ class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMi shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -274,9 +281,11 @@ class _OutlineButton extends StatefulWidget { this.shape, this.clipBehavior, this.focusNode, + this.autofocus = false, this.child, }) : assert(highlightElevation != null && highlightElevation >= 0.0), assert(highlightedBorderColor != null), + assert(autofocus != null), super(key: key); final VoidCallback onPressed; @@ -297,6 +306,7 @@ class _OutlineButton extends StatefulWidget { final ShapeBorder shape; final Clip clipBehavior; final FocusNode focusNode; + final bool autofocus; final Widget child; bool get enabled => onPressed != null; diff --git a/packages/flutter/lib/src/material/raised_button.dart b/packages/flutter/lib/src/material/raised_button.dart index 7d6d1c478b..dfb4cc5c4b 100644 --- a/packages/flutter/lib/src/material/raised_button.dart +++ b/packages/flutter/lib/src/material/raised_button.dart @@ -101,9 +101,10 @@ import 'theme_data.dart'; class RaisedButton extends MaterialButton { /// Create a filled button. /// - /// The [elevation], [highlightElevation], [disabledElevation], and - /// [clipBehavior] arguments must not be null. Additionally, [elevation], - /// [highlightElevation], and [disabledElevation] must be non-negative. + /// The [autofocus] and [clipBehavior] arguments must not be null. + /// Additionally, [elevation], [hoverElevation], [focusElevation], + /// [highlightElevation], and [disabledElevation] must be non-negative, if + /// specified. const RaisedButton({ Key key, @required VoidCallback onPressed, @@ -127,10 +128,12 @@ class RaisedButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus = false, MaterialTapTargetSize materialTapTargetSize, Duration animationDuration, Widget child, - }) : assert(elevation == null || elevation >= 0.0), + }) : assert(autofocus != null), + assert(elevation == null || elevation >= 0.0), assert(focusElevation == null || focusElevation >= 0.0), assert(hoverElevation == null || hoverElevation >= 0.0), assert(highlightElevation == null || highlightElevation >= 0.0), @@ -158,6 +161,7 @@ class RaisedButton extends MaterialButton { shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, animationDuration: animationDuration, child: child, @@ -191,6 +195,7 @@ class RaisedButton extends MaterialButton { ShapeBorder shape, Clip clipBehavior, FocusNode focusNode, + bool autofocus, MaterialTapTargetSize materialTapTargetSize, Duration animationDuration, @required Widget icon, @@ -220,6 +225,7 @@ class RaisedButton extends MaterialButton { constraints: buttonTheme.getConstraints(this), shape: buttonTheme.getShape(this), focusNode: focusNode, + autofocus: autofocus, animationDuration: buttonTheme.getAnimationDuration(this), materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this), child: child, @@ -262,6 +268,7 @@ class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixi ShapeBorder shape, Clip clipBehavior = Clip.none, FocusNode focusNode, + bool autofocus = false, MaterialTapTargetSize materialTapTargetSize, Duration animationDuration, @required Widget icon, @@ -271,6 +278,7 @@ class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixi assert(disabledElevation == null || disabledElevation >= 0.0), assert(icon != null), assert(label != null), + assert(autofocus != null), super( key: key, onPressed: onPressed, @@ -291,6 +299,7 @@ class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixi shape: shape, clipBehavior: clipBehavior, focusNode: focusNode, + autofocus: autofocus, materialTapTargetSize: materialTapTargetSize, animationDuration: animationDuration, child: Row( diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index 0e10d9dd7e..d16e7ec39f 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -184,7 +184,10 @@ class SelectableText extends StatefulWidget { /// If the [style] argument is null, the text will use the style from the /// closest enclosing [DefaultTextStyle]. /// - /// The [data] parameter must not be null. + + /// The [showCursor], [autofocus], [dragStartBehavior], and [data] parameters + /// must not be null. If specified, the [maxLines] argument must be greater + /// than zero. const SelectableText( this.data, { Key key, @@ -225,6 +228,8 @@ class SelectableText extends StatefulWidget { /// /// The [textSpan] parameter must not be null and only contain [TextSpan] in /// [textSpan.children]. Other type of [InlineSpan] is not allowed. + /// + /// The [autofocus] and [dragStartBehavior] arguments must not be null. const SelectableText.rich( this.textSpan, { Key key, diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart index a25f9d3d1d..c3800a5bff 100644 --- a/packages/flutter/lib/src/material/toggle_buttons.dart +++ b/packages/flutter/lib/src/material/toggle_buttons.dart @@ -679,13 +679,7 @@ class _ToggleButton extends StatelessWidget { /// The splash color for the button's [InkWell]. final Color splashColor; - /// A leaf node in the focus tree for this button. - /// - /// Focus is used to determine which widget should be affected by keyboard - /// events. The focus tree keeps track of which widget is currently focused - /// on by the user. - /// - /// See [FocusNode] for more information about how focus nodes are used. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; /// Called when the button is tapped or otherwise activated. diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 150e0e864a..3f4e4d34e0 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -339,9 +339,12 @@ class EditableText extends StatefulWidget { /// The text cursor is not shown if [showCursor] is false or if [showCursor] /// is null (the default) and [readOnly] is true. /// - /// The [controller], [focusNode], [style], [cursorColor], [backgroundCursorColor], - /// [textAlign], [dragStartBehavior], [rendererIgnoresPointer] and [readOnly] - /// arguments must not be null. + /// The [controller], [focusNode], [obscureText], [autocorrect], [autofocus], + /// [showSelectionHandles], [enableInteractiveSelection], [forceLine], + /// [style], [cursorColor], [cursorOpacityAnimates],[backgroundCursorColor], + /// [paintCursorAboveText], [textAlign], [dragStartBehavior], [scrollPadding], + /// [dragStartBehavior], [toolbarOptions], [rendererIgnoresPointer], and + /// [readOnly] arguments must not be null. EditableText({ Key key, @required this.controller, diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index fd50e9ece5..bdf09a1d74 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -137,7 +137,7 @@ class Focus extends StatefulWidget { /// /// The [child] argument is required and must not be null. /// - /// The [autofocus] argument must not be null. + /// The [autofocus] and [skipTraversal] arguments must not be null. const Focus({ Key key, @required this.child, @@ -190,24 +190,33 @@ class Focus extends StatefulWidget { /// focus. final ValueChanged onFocusChange; + /// {@template flutter.widgets.Focus.autofocus} /// True if this widget will be selected as the initial focus when no other /// node in its scope is currently focused. /// - /// Ideally, there is only one [Focus] with autofocus set in each - /// [FocusScope]. If there is more than one [Focus] with autofocus set, then - /// the first one added to the tree will get focus. + /// Ideally, there is only one widget with autofocus set in each [FocusScope]. + /// If there is more than one widget with autofocus set, then the first one + /// added to the tree will get focus. + /// + /// Must not be null. Defaults to false. + /// {@endtemplate} final bool autofocus; - /// An optional focus node to use as the focus node for this [Focus] widget. + /// {@template flutter.widgets.Focus.focusNode} + /// An optional focus node to use as the focus node for this widget. /// - /// If one is not supplied, then one will be allocated and owned by this - /// widget. + /// If one is not supplied, then one will be automatically allocated, owned, + /// and managed by this widget. The widget will be focusable even if a + /// [focusNode] is not supplied. If supplied, the given `focusNode` will be + /// _hosted_ by this widget, but not owned. See [FocusNode] for more + /// information on what being hosted and/or owned implies. /// /// Supplying a focus node is sometimes useful if an ancestor to this widget /// wants to control when this widget has the focus. The owner will be /// responsible for calling [FocusNode.dispose] on the focus node when it is - /// done with it, but this [Focus] widget will attach/detach and reparent the - /// node when needed. + /// done with it, but this widget will attach/detach and reparent the node + /// when needed. + /// {@endtemplate} final FocusNode focusNode; /// Sets the [FocusNode.skipTraversal] flag on the focus node so that it won't diff --git a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart index abb3c7e263..a2e7e09ca9 100644 --- a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart +++ b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart @@ -31,18 +31,27 @@ class RawKeyboardListener extends StatefulWidget { /// /// For text entry, consider using a [EditableText], which integrates with /// on-screen keyboards and input method editors (IMEs). + /// + /// The [focusNode] and [child] arguments are required and must not be null. + /// + /// The [autofocus] argument must not be null. const RawKeyboardListener({ Key key, @required this.focusNode, - @required this.onKey, + this.autofocus = false, + this.onKey, @required this.child, }) : assert(focusNode != null), + assert(autofocus != null), assert(child != null), super(key: key); - /// Controls whether this widget has keyboard focus. + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode focusNode; + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + /// Called whenever this widget receives a raw keyboard event. final ValueChanged onKey; @@ -113,5 +122,11 @@ class _RawKeyboardListenerState extends State { } @override - Widget build(BuildContext context) => Focus(focusNode: widget.focusNode, child: widget.child); + Widget build(BuildContext context) { + return Focus( + focusNode: widget.focusNode, + autofocus: widget.autofocus, + child: widget.child, + ); + } }