"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/button.dart" (13 Nov 2020, 18984 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:math' as math;
    8 
    9 import 'package:flutter/foundation.dart';
   10 import 'package:flutter/gestures.dart';
   11 import 'package:flutter/rendering.dart';
   12 import 'package:flutter/widgets.dart';
   13 
   14 import 'button_theme.dart';
   15 import 'constants.dart';
   16 import 'ink_well.dart';
   17 import 'material.dart';
   18 import 'material_state.dart';
   19 import 'theme.dart';
   20 import 'theme_data.dart';
   21 
   22 /// Creates a button based on [Semantics], [Material], and [InkWell]
   23 /// widgets.
   24 ///
   25 /// This class does not use the current [Theme] or [ButtonTheme] to
   26 /// compute default values for unspecified parameters. It's intended to
   27 /// be used for custom Material buttons that optionally incorporate defaults
   28 /// from the themes or from app-specific sources.
   29 ///
   30 /// [RaisedButton] and [FlatButton] configure a [RawMaterialButton] based
   31 /// on the current [Theme] and [ButtonTheme].
   32 @Category(<String>['Material', 'Button'])
   33 class RawMaterialButton extends StatefulWidget {
   34   /// Create a button based on [Semantics], [Material], and [InkWell] widgets.
   35   ///
   36   /// The [shape], [elevation], [focusElevation], [hoverElevation],
   37   /// [highlightElevation], [disabledElevation], [padding], [constraints],
   38   /// [autofocus], and [clipBehavior] arguments must not be null. Additionally,
   39   /// [elevation], [focusElevation], [hoverElevation], [highlightElevation], and
   40   /// [disabledElevation] must be non-negative.
   41   const RawMaterialButton({
   42     Key key,
   43     @required this.onPressed,
   44     this.onLongPress,
   45     this.onHighlightChanged,
   46     this.mouseCursor,
   47     this.textStyle,
   48     this.fillColor,
   49     this.focusColor,
   50     this.hoverColor,
   51     this.highlightColor,
   52     this.splashColor,
   53     this.elevation = 2.0,
   54     this.focusElevation = 4.0,
   55     this.hoverElevation = 4.0,
   56     this.highlightElevation = 8.0,
   57     this.disabledElevation = 0.0,
   58     this.padding = EdgeInsets.zero,
   59     this.visualDensity = const VisualDensity(),
   60     this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
   61     this.shape = const RoundedRectangleBorder(),
   62     this.animationDuration = kThemeChangeDuration,
   63     this.clipBehavior = Clip.none,
   64     this.focusNode,
   65     this.autofocus = false,
   66     MaterialTapTargetSize materialTapTargetSize,
   67     this.child,
   68     this.enableFeedback = true,
   69   }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
   70        assert(shape != null),
   71        assert(elevation != null && elevation >= 0.0),
   72        assert(focusElevation != null && focusElevation >= 0.0),
   73        assert(hoverElevation != null && hoverElevation >= 0.0),
   74        assert(highlightElevation != null && highlightElevation >= 0.0),
   75        assert(disabledElevation != null && disabledElevation >= 0.0),
   76        assert(padding != null),
   77        assert(constraints != null),
   78        assert(animationDuration != null),
   79        assert(clipBehavior != null),
   80        assert(autofocus != null),
   81        super(key: key);
   82 
   83   /// Called when the button is tapped or otherwise activated.
   84   ///
   85   /// If this callback and [onLongPress] are null, then the button will be disabled.
   86   ///
   87   /// See also:
   88   ///
   89   ///  * [enabled], which is true if the button is enabled.
   90   final VoidCallback onPressed;
   91 
   92   /// Called when the button is long-pressed.
   93   ///
   94   /// If this callback and [onPressed] are null, then the button will be disabled.
   95   ///
   96   /// See also:
   97   ///
   98   ///  * [enabled], which is true if the button is enabled.
   99   final VoidCallback onLongPress;
  100 
  101   /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
  102   /// callback.
  103   ///
  104   /// If [onPressed] changes from null to non-null while a gesture is ongoing,
  105   /// this can fire during the build phase (in which case calling
  106   /// [State.setState] is not allowed).
  107   final ValueChanged<bool> onHighlightChanged;
  108 
  109   /// {@template flutter.material.button.mouseCursor}
  110   /// The cursor for a mouse pointer when it enters or is hovering over the
  111   /// button.
  112   ///
  113   /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  114   /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  115   ///
  116   ///  * [MaterialState.pressed].
  117   ///  * [MaterialState.hovered].
  118   ///  * [MaterialState.focused].
  119   ///  * [MaterialState.disabled].
  120   ///
  121   /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
  122   /// {@endtemplate}
  123   final MouseCursor mouseCursor;
  124 
  125   /// Defines the default text style, with [Material.textStyle], for the
  126   /// button's [child].
  127   ///
  128   /// If [TextStyle.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
  129   /// is used for the following [MaterialState]s:
  130   ///
  131   ///  * [MaterialState.pressed].
  132   ///  * [MaterialState.hovered].
  133   ///  * [MaterialState.focused].
  134   ///  * [MaterialState.disabled].
  135   final TextStyle textStyle;
  136 
  137   /// The color of the button's [Material].
  138   final Color fillColor;
  139 
  140   /// The color for the button's [Material] when it has the input focus.
  141   final Color focusColor;
  142 
  143   /// The color for the button's [Material] when a pointer is hovering over it.
  144   final Color hoverColor;
  145 
  146   /// The highlight color for the button's [InkWell].
  147   final Color highlightColor;
  148 
  149   /// The splash color for the button's [InkWell].
  150   final Color splashColor;
  151 
  152   /// The elevation for the button's [Material] when the button
  153   /// is [enabled] but not pressed.
  154   ///
  155   /// Defaults to 2.0. The value is always non-negative.
  156   ///
  157   /// See also:
  158   ///
  159   ///  * [highlightElevation], the default elevation.
  160   ///  * [hoverElevation], the elevation when a pointer is hovering over the
  161   ///    button.
  162   ///  * [focusElevation], the elevation when the button is focused.
  163   ///  * [disabledElevation], the elevation when the button is disabled.
  164   final double elevation;
  165 
  166   /// The elevation for the button's [Material] when the button
  167   /// is [enabled] and a pointer is hovering over it.
  168   ///
  169   /// Defaults to 4.0. The value is always non-negative.
  170   ///
  171   /// If the button is [enabled], and being pressed (in the highlighted state),
  172   /// then the [highlightElevation] take precedence over the [hoverElevation].
  173   ///
  174   /// See also:
  175   ///
  176   ///  * [elevation], the default elevation.
  177   ///  * [focusElevation], the elevation when the button is focused.
  178   ///  * [disabledElevation], the elevation when the button is disabled.
  179   ///  * [highlightElevation], the elevation when the button is pressed.
  180   final double hoverElevation;
  181 
  182   /// The elevation for the button's [Material] when the button
  183   /// is [enabled] and has the input focus.
  184   ///
  185   /// Defaults to 4.0. The value is always non-negative.
  186   ///
  187   /// If the button is [enabled], and being pressed (in the highlighted state),
  188   /// or a mouse cursor is hovering over the button, then the [hoverElevation]
  189   /// and [highlightElevation] take precedence over the [focusElevation].
  190   ///
  191   /// See also:
  192   ///
  193   ///  * [elevation], the default elevation.
  194   ///  * [hoverElevation], the elevation when a pointer is hovering over the
  195   ///    button.
  196   ///  * [disabledElevation], the elevation when the button is disabled.
  197   ///  * [highlightElevation], the elevation when the button is pressed.
  198   final double focusElevation;
  199 
  200   /// The elevation for the button's [Material] when the button
  201   /// is [enabled] and pressed.
  202   ///
  203   /// Defaults to 8.0. The value is always non-negative.
  204   ///
  205   /// See also:
  206   ///
  207   ///  * [elevation], the default elevation.
  208   ///  * [hoverElevation], the elevation when a pointer is hovering over the
  209   ///    button.
  210   ///  * [focusElevation], the elevation when the button is focused.
  211   ///  * [disabledElevation], the elevation when the button is disabled.
  212   final double highlightElevation;
  213 
  214   /// The elevation for the button's [Material] when the button
  215   /// is not [enabled].
  216   ///
  217   /// Defaults to 0.0. The value is always non-negative.
  218   ///
  219   /// See also:
  220   ///
  221   ///  * [elevation], the default elevation.
  222   ///  * [hoverElevation], the elevation when a pointer is hovering over the
  223   ///    button.
  224   ///  * [focusElevation], the elevation when the button is focused.
  225   ///  * [highlightElevation], the elevation when the button is pressed.
  226   final double disabledElevation;
  227 
  228   /// The internal padding for the button's [child].
  229   final EdgeInsetsGeometry padding;
  230 
  231   /// Defines how compact the button's layout will be.
  232   ///
  233   /// {@macro flutter.material.themedata.visualDensity}
  234   ///
  235   /// See also:
  236   ///
  237   ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
  238   ///    within a [Theme].
  239   final VisualDensity visualDensity;
  240 
  241   /// Defines the button's size.
  242   ///
  243   /// Typically used to constrain the button's minimum size.
  244   final BoxConstraints constraints;
  245 
  246   /// The shape of the button's [Material].
  247   ///
  248   /// The button's highlight and splash are clipped to this shape. If the
  249   /// button has an elevation, then its drop shadow is defined by this shape.
  250   ///
  251   /// If [shape] is a [MaterialStateProperty<ShapeBorder>], [MaterialStateProperty.resolve]
  252   /// is used for the following [MaterialState]s:
  253   ///
  254   /// * [MaterialState.pressed].
  255   /// * [MaterialState.hovered].
  256   /// * [MaterialState.focused].
  257   /// * [MaterialState.disabled].
  258   final ShapeBorder shape;
  259 
  260   /// Defines the duration of animated changes for [shape] and [elevation].
  261   ///
  262   /// The default value is [kThemeChangeDuration].
  263   final Duration animationDuration;
  264 
  265   /// Typically the button's label.
  266   final Widget child;
  267 
  268   /// Whether the button is enabled or disabled.
  269   ///
  270   /// Buttons are disabled by default. To enable a button, set its [onPressed]
  271   /// or [onLongPress] properties to a non-null value.
  272   bool get enabled => onPressed != null || onLongPress != null;
  273 
  274   /// Configures the minimum size of the tap target.
  275   ///
  276   /// Defaults to [MaterialTapTargetSize.padded].
  277   ///
  278   /// See also:
  279   ///
  280   ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
  281   final MaterialTapTargetSize materialTapTargetSize;
  282 
  283   /// {@macro flutter.widgets.Focus.focusNode}
  284   final FocusNode focusNode;
  285 
  286   /// {@macro flutter.widgets.Focus.autofocus}
  287   final bool autofocus;
  288 
  289   /// {@macro flutter.widgets.Clip}
  290   ///
  291   /// Defaults to [Clip.none], and must not be null.
  292   final Clip clipBehavior;
  293 
  294   /// Whether detected gestures should provide acoustic and/or haptic feedback.
  295   ///
  296   /// For example, on Android a tap will produce a clicking sound and a
  297   /// long-press will produce a short vibration, when feedback is enabled.
  298   ///
  299   /// See also:
  300   ///
  301   ///  * [Feedback] for providing platform-specific feedback to certain actions.
  302   final bool enableFeedback;
  303 
  304   @override
  305   _RawMaterialButtonState createState() => _RawMaterialButtonState();
  306 }
  307 
  308 class _RawMaterialButtonState extends State<RawMaterialButton> {
  309   final Set<MaterialState> _states = <MaterialState>{};
  310 
  311   bool get _hovered => _states.contains(MaterialState.hovered);
  312   bool get _focused => _states.contains(MaterialState.focused);
  313   bool get _pressed => _states.contains(MaterialState.pressed);
  314   bool get _disabled => _states.contains(MaterialState.disabled);
  315 
  316   void _updateState(MaterialState state, bool value) {
  317     value ? _states.add(state) : _states.remove(state);
  318   }
  319 
  320   void _handleHighlightChanged(bool value) {
  321     if (_pressed != value) {
  322       setState(() {
  323         _updateState(MaterialState.pressed, value);
  324         if (widget.onHighlightChanged != null) {
  325           widget.onHighlightChanged(value);
  326         }
  327       });
  328     }
  329   }
  330 
  331   void _handleHoveredChanged(bool value) {
  332     if (_hovered != value) {
  333       setState(() {
  334         _updateState(MaterialState.hovered, value);
  335       });
  336     }
  337   }
  338 
  339   void _handleFocusedChanged(bool value) {
  340     if (_focused != value) {
  341       setState(() {
  342         _updateState(MaterialState.focused, value);
  343       });
  344     }
  345   }
  346 
  347   @override
  348   void initState() {
  349     super.initState();
  350     _updateState(MaterialState.disabled, !widget.enabled);
  351   }
  352 
  353   @override
  354   void didUpdateWidget(RawMaterialButton oldWidget) {
  355     super.didUpdateWidget(oldWidget);
  356     _updateState(MaterialState.disabled, !widget.enabled);
  357     // If the button is disabled while a press gesture is currently ongoing,
  358     // InkWell makes a call to handleHighlightChanged. This causes an exception
  359     // because it calls setState in the middle of a build. To preempt this, we
  360     // manually update pressed to false when this situation occurs.
  361     if (_disabled && _pressed) {
  362       _handleHighlightChanged(false);
  363     }
  364   }
  365 
  366   double get _effectiveElevation {
  367     // These conditionals are in order of precedence, so be careful about
  368     // reorganizing them.
  369     if (_disabled) {
  370       return widget.disabledElevation;
  371     }
  372     if (_pressed) {
  373       return widget.highlightElevation;
  374     }
  375     if (_hovered) {
  376       return widget.hoverElevation;
  377     }
  378     if (_focused) {
  379       return widget.focusElevation;
  380     }
  381     return widget.elevation;
  382   }
  383 
  384   @override
  385   Widget build(BuildContext context) {
  386     final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(widget.textStyle?.color, _states);
  387     final ShapeBorder effectiveShape =  MaterialStateProperty.resolveAs<ShapeBorder>(widget.shape, _states);
  388     final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
  389     final BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
  390     final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
  391       widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
  392       _states,
  393     );
  394     final EdgeInsetsGeometry padding = widget.padding.add(
  395       EdgeInsets.only(
  396         left: densityAdjustment.dx,
  397         top: densityAdjustment.dy,
  398         right: densityAdjustment.dx,
  399         bottom: densityAdjustment.dy,
  400       ),
  401     ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
  402 
  403 
  404     final Widget result = ConstrainedBox(
  405       constraints: effectiveConstraints,
  406       child: Material(
  407         elevation: _effectiveElevation,
  408         textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
  409         shape: effectiveShape,
  410         color: widget.fillColor,
  411         type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
  412         animationDuration: widget.animationDuration,
  413         clipBehavior: widget.clipBehavior,
  414         child: InkWell(
  415           focusNode: widget.focusNode,
  416           canRequestFocus: widget.enabled,
  417           onFocusChange: _handleFocusedChanged,
  418           autofocus: widget.autofocus,
  419           onHighlightChanged: _handleHighlightChanged,
  420           splashColor: widget.splashColor,
  421           highlightColor: widget.highlightColor,
  422           focusColor: widget.focusColor,
  423           hoverColor: widget.hoverColor,
  424           onHover: _handleHoveredChanged,
  425           onTap: widget.onPressed,
  426           onLongPress: widget.onLongPress,
  427           enableFeedback: widget.enableFeedback,
  428           customBorder: effectiveShape,
  429           mouseCursor: effectiveMouseCursor,
  430           child: IconTheme.merge(
  431             data: IconThemeData(color: effectiveTextColor),
  432             child: Container(
  433               padding: padding,
  434               child: Center(
  435                 widthFactor: 1.0,
  436                 heightFactor: 1.0,
  437                 child: widget.child,
  438               ),
  439             ),
  440           ),
  441         ),
  442       ),
  443     );
  444     Size minSize;
  445     switch (widget.materialTapTargetSize) {
  446       case MaterialTapTargetSize.padded:
  447         minSize = Size(
  448           kMinInteractiveDimension + densityAdjustment.dx,
  449           kMinInteractiveDimension + densityAdjustment.dy,
  450         );
  451         assert(minSize.width >= 0.0);
  452         assert(minSize.height >= 0.0);
  453         break;
  454       case MaterialTapTargetSize.shrinkWrap:
  455         minSize = Size.zero;
  456         break;
  457     }
  458 
  459     return Semantics(
  460       container: true,
  461       button: true,
  462       enabled: widget.enabled,
  463       child: _InputPadding(
  464         minSize: minSize,
  465         child: result,
  466       ),
  467     );
  468   }
  469 }
  470 
  471 /// A widget to pad the area around a [MaterialButton]'s inner [Material].
  472 ///
  473 /// Redirect taps that occur in the padded area around the child to the center
  474 /// of the child. This increases the size of the button and the button's
  475 /// "tap target", but not its material or its ink splashes.
  476 class _InputPadding extends SingleChildRenderObjectWidget {
  477   const _InputPadding({
  478     Key key,
  479     Widget child,
  480     this.minSize,
  481   }) : super(key: key, child: child);
  482 
  483   final Size minSize;
  484 
  485   @override
  486   RenderObject createRenderObject(BuildContext context) {
  487     return _RenderInputPadding(minSize);
  488   }
  489 
  490   @override
  491   void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
  492     renderObject.minSize = minSize;
  493   }
  494 }
  495 
  496 class _RenderInputPadding extends RenderShiftedBox {
  497   _RenderInputPadding(this._minSize, [RenderBox child]) : super(child);
  498 
  499   Size get minSize => _minSize;
  500   Size _minSize;
  501   set minSize(Size value) {
  502     if (_minSize == value)
  503       return;
  504     _minSize = value;
  505     markNeedsLayout();
  506   }
  507 
  508   @override
  509   double computeMinIntrinsicWidth(double height) {
  510     if (child != null)
  511       return math.max(child.getMinIntrinsicWidth(height), minSize.width);
  512     return 0.0;
  513   }
  514 
  515   @override
  516   double computeMinIntrinsicHeight(double width) {
  517     if (child != null)
  518       return math.max(child.getMinIntrinsicHeight(width), minSize.height);
  519     return 0.0;
  520   }
  521 
  522   @override
  523   double computeMaxIntrinsicWidth(double height) {
  524     if (child != null)
  525       return math.max(child.getMaxIntrinsicWidth(height), minSize.width);
  526     return 0.0;
  527   }
  528 
  529   @override
  530   double computeMaxIntrinsicHeight(double width) {
  531     if (child != null)
  532       return math.max(child.getMaxIntrinsicHeight(width), minSize.height);
  533     return 0.0;
  534   }
  535 
  536   @override
  537   void performLayout() {
  538     final BoxConstraints constraints = this.constraints;
  539     if (child != null) {
  540       child.layout(constraints, parentUsesSize: true);
  541       final double height = math.max(child.size.width, minSize.width);
  542       final double width = math.max(child.size.height, minSize.height);
  543       size = constraints.constrain(Size(height, width));
  544       final BoxParentData childParentData = child.parentData as BoxParentData;
  545       childParentData.offset = Alignment.center.alongOffset(size - child.size as Offset);
  546     } else {
  547       size = Size.zero;
  548     }
  549   }
  550 
  551   @override
  552   bool hitTest(BoxHitTestResult result, { Offset position }) {
  553     if (super.hitTest(result, position: position)) {
  554       return true;
  555     }
  556     final Offset center = child.size.center(Offset.zero);
  557     return result.addWithRawTransform(
  558       transform: MatrixUtils.forceToPoint(center),
  559       position: center,
  560       hitTest: (BoxHitTestResult result, Offset position) {
  561         assert(position == center);
  562         return child.hitTest(result, position: center);
  563       },
  564     );
  565   }
  566 }