"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/outline_button.dart" (13 Nov 2020, 20407 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 'package:flutter/foundation.dart';
    8 import 'package:flutter/rendering.dart';
    9 import 'package:flutter/widgets.dart';
   10 
   11 import 'button_theme.dart';
   12 import 'colors.dart';
   13 import 'material_button.dart';
   14 import 'material_state.dart';
   15 import 'raised_button.dart';
   16 import 'theme.dart';
   17 import 'theme_data.dart';
   18 
   19 // The total time to make the button's fill color opaque and change
   20 // its elevation. Only applies when highlightElevation > 0.0.
   21 const Duration _kPressDuration = Duration(milliseconds: 150);
   22 
   23 // Half of _kPressDuration: just the time to change the button's
   24 // elevation. Only applies when highlightElevation > 0.0.
   25 const Duration _kElevationDuration = Duration(milliseconds: 75);
   26 
   27 /// Similar to a [FlatButton] with a thin grey rounded rectangle border.
   28 ///
   29 /// ### This class is obsolete, please use [OutlinedButton] instead.
   30 ///
   31 /// FlatButton, RaisedButton, and OutlineButton have been replaced by
   32 /// TextButton, ElevatedButton, and OutlinedButton respectively.
   33 /// ButtonTheme has been replaced by TextButtonTheme,
   34 /// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
   35 /// will be deprecated soon, please migrate code that uses them.
   36 /// There's a detailed migration guide for the new button and button
   37 /// theme classes in
   38 /// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
   39 ///
   40 /// The outline button's border shape is defined by [shape]
   41 /// and its appearance is defined by [borderSide], [disabledBorderColor],
   42 /// and [highlightedBorderColor]. By default the border is a one pixel
   43 /// wide grey rounded rectangle that does not change when the button is
   44 /// pressed or disabled. By default the button's background is transparent.
   45 ///
   46 /// If the [onPressed] or [onLongPress] callbacks are null, then the button will be disabled and by
   47 /// default will resemble a flat button in the [disabledColor].
   48 ///
   49 /// The button's [highlightElevation], which defines the size of the
   50 /// drop shadow when the button is pressed, is 0.0 (no shadow) by default.
   51 /// If [highlightElevation] is given a value greater than 0.0 then the button
   52 /// becomes a cross between [RaisedButton] and [FlatButton]: a bordered
   53 /// button whose elevation increases and whose background becomes opaque
   54 /// when the button is pressed.
   55 ///
   56 /// If you want an ink-splash effect for taps, but don't want to use a button,
   57 /// consider using [InkWell] directly.
   58 ///
   59 /// Outline buttons have a minimum size of 88.0 by 36.0 which can be overridden
   60 /// with [ButtonTheme].
   61 ///
   62 /// {@tool dartpad --template=stateless_widget_scaffold_center}
   63 ///
   64 /// Here is an example of a basic [OutlineButton].
   65 ///
   66 /// ```dart
   67 ///   Widget build(BuildContext context) {
   68 ///     return OutlineButton(
   69 ///       onPressed: () {
   70 ///         print('Received click');
   71 ///       },
   72 ///       child: Text('Click Me'),
   73 ///     );
   74 ///   }
   75 /// ```
   76 /// {@end-tool}
   77 ///
   78 /// See also:
   79 ///
   80 ///  * [RaisedButton], a filled material design button with a shadow.
   81 ///  * [FlatButton], a material design button without a shadow.
   82 ///  * [DropdownButton], a button that shows options to select from.
   83 ///  * [FloatingActionButton], the round button in material applications.
   84 ///  * [IconButton], to create buttons that just contain icons.
   85 ///  * [InkWell], which implements the ink splash part of a flat button.
   86 ///  * <https://material.io/design/components/buttons.html>
   87 class OutlineButton extends MaterialButton {
   88   /// Create an outline button.
   89   ///
   90   /// The [highlightElevation] argument must be null or a positive value
   91   /// and the [autofocus] and [clipBehavior] arguments must not be null.
   92   const OutlineButton({
   93     Key key,
   94     @required VoidCallback onPressed,
   95     VoidCallback onLongPress,
   96     MouseCursor mouseCursor,
   97     ButtonTextTheme textTheme,
   98     Color textColor,
   99     Color disabledTextColor,
  100     Color color,
  101     Color focusColor,
  102     Color hoverColor,
  103     Color highlightColor,
  104     Color splashColor,
  105     double highlightElevation,
  106     this.borderSide,
  107     this.disabledBorderColor,
  108     this.highlightedBorderColor,
  109     EdgeInsetsGeometry padding,
  110     VisualDensity visualDensity,
  111     ShapeBorder shape,
  112     Clip clipBehavior = Clip.none,
  113     FocusNode focusNode,
  114     bool autofocus = false,
  115     MaterialTapTargetSize materialTapTargetSize,
  116     Widget child,
  117   }) : assert(highlightElevation == null || highlightElevation >= 0.0),
  118        assert(clipBehavior != null),
  119        assert(autofocus != null),
  120        super(
  121          key: key,
  122          onPressed: onPressed,
  123          onLongPress: onLongPress,
  124          mouseCursor: mouseCursor,
  125          textTheme: textTheme,
  126          textColor: textColor,
  127          disabledTextColor: disabledTextColor,
  128          color: color,
  129          focusColor: focusColor,
  130          hoverColor: hoverColor,
  131          highlightColor: highlightColor,
  132          splashColor: splashColor,
  133          highlightElevation: highlightElevation,
  134          padding: padding,
  135          visualDensity: visualDensity,
  136          shape: shape,
  137          clipBehavior: clipBehavior,
  138          focusNode: focusNode,
  139          materialTapTargetSize: materialTapTargetSize,
  140          autofocus: autofocus,
  141          child: child,
  142        );
  143 
  144   /// Create an outline button from a pair of widgets that serve as the button's
  145   /// [icon] and [label].
  146   ///
  147   /// The icon and label are arranged in a row and padded by 12 logical pixels
  148   /// at the start, and 16 at the end, with an 8 pixel gap in between.
  149   ///
  150   /// The [highlightElevation] argument must be null or a positive value. The
  151   /// [icon], [label], [autofocus], and [clipBehavior] arguments must not be null.
  152   factory OutlineButton.icon({
  153     Key key,
  154     @required VoidCallback onPressed,
  155     VoidCallback onLongPress,
  156     MouseCursor mouseCursor,
  157     ButtonTextTheme textTheme,
  158     Color textColor,
  159     Color disabledTextColor,
  160     Color color,
  161     Color focusColor,
  162     Color hoverColor,
  163     Color highlightColor,
  164     Color splashColor,
  165     double highlightElevation,
  166     Color highlightedBorderColor,
  167     Color disabledBorderColor,
  168     BorderSide borderSide,
  169     EdgeInsetsGeometry padding,
  170     VisualDensity visualDensity,
  171     ShapeBorder shape,
  172     Clip clipBehavior,
  173     FocusNode focusNode,
  174     bool autofocus,
  175     MaterialTapTargetSize materialTapTargetSize,
  176     @required Widget icon,
  177     @required Widget label,
  178   }) = _OutlineButtonWithIcon;
  179 
  180   /// The outline border's color when the button is [enabled] and pressed.
  181   ///
  182   /// By default the border's color does not change when the button
  183   /// is pressed.
  184   ///
  185   /// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
  186   final Color highlightedBorderColor;
  187 
  188   /// The outline border's color when the button is not [enabled].
  189   ///
  190   /// By default the outline border's color does not change when the
  191   /// button is disabled.
  192   ///
  193   /// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
  194   final Color disabledBorderColor;
  195 
  196   /// Defines the color of the border when the button is enabled but not
  197   /// pressed, and the border outline's width and style in general.
  198   ///
  199   /// If the border side's [BorderSide.style] is [BorderStyle.none], then
  200   /// an outline is not drawn.
  201   ///
  202   /// If null the default border's style is [BorderStyle.solid], its
  203   /// [BorderSide.width] is 1.0, and its color is a light shade of grey.
  204   ///
  205   /// If [BorderSide.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
  206   /// is used in all states and both [highlightedBorderColor] and [disabledBorderColor]
  207   /// are ignored.
  208   final BorderSide borderSide;
  209 
  210   @override
  211   Widget build(BuildContext context) {
  212     final ButtonThemeData buttonTheme = ButtonTheme.of(context);
  213     return _OutlineButton(
  214       autofocus: autofocus,
  215       onPressed: onPressed,
  216       onLongPress: onLongPress,
  217       mouseCursor: mouseCursor,
  218       brightness: buttonTheme.getBrightness(this),
  219       textTheme: textTheme,
  220       textColor: buttonTheme.getTextColor(this),
  221       disabledTextColor: buttonTheme.getDisabledTextColor(this),
  222       color: color,
  223       focusColor: buttonTheme.getFocusColor(this),
  224       hoverColor: buttonTheme.getHoverColor(this),
  225       highlightColor: buttonTheme.getHighlightColor(this),
  226       splashColor: buttonTheme.getSplashColor(this),
  227       highlightElevation: buttonTheme.getHighlightElevation(this),
  228       borderSide: borderSide,
  229       disabledBorderColor: disabledBorderColor,
  230       highlightedBorderColor: highlightedBorderColor ?? buttonTheme.colorScheme.primary,
  231       padding: buttonTheme.getPadding(this),
  232       visualDensity: visualDensity,
  233       shape: buttonTheme.getShape(this),
  234       clipBehavior: clipBehavior,
  235       focusNode: focusNode,
  236       materialTapTargetSize: materialTapTargetSize,
  237       child: child,
  238     );
  239   }
  240 
  241   @override
  242   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  243     super.debugFillProperties(properties);
  244     properties.add(DiagnosticsProperty<BorderSide>('borderSide', borderSide, defaultValue: null));
  245     properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
  246     properties.add(ColorProperty('highlightedBorderColor', highlightedBorderColor, defaultValue: null));
  247   }
  248 }
  249 
  250 // The type of OutlineButtons created with OutlineButton.icon.
  251 //
  252 // This class only exists to give OutlineButtons created with OutlineButton.icon
  253 // a distinct class for the sake of ButtonTheme. It can not be instantiated.
  254 class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin {
  255   _OutlineButtonWithIcon({
  256     Key key,
  257     @required VoidCallback onPressed,
  258     VoidCallback onLongPress,
  259     MouseCursor mouseCursor,
  260     ButtonTextTheme textTheme,
  261     Color textColor,
  262     Color disabledTextColor,
  263     Color color,
  264     Color focusColor,
  265     Color hoverColor,
  266     Color highlightColor,
  267     Color splashColor,
  268     double highlightElevation,
  269     Color highlightedBorderColor,
  270     Color disabledBorderColor,
  271     BorderSide borderSide,
  272     EdgeInsetsGeometry padding,
  273     VisualDensity visualDensity,
  274     ShapeBorder shape,
  275     Clip clipBehavior = Clip.none,
  276     FocusNode focusNode,
  277     bool autofocus = false,
  278     MaterialTapTargetSize materialTapTargetSize,
  279     @required Widget icon,
  280     @required Widget label,
  281   }) : assert(highlightElevation == null || highlightElevation >= 0.0),
  282        assert(clipBehavior != null),
  283        assert(autofocus != null),
  284        assert(icon != null),
  285        assert(label != null),
  286        super(
  287          key: key,
  288          onPressed: onPressed,
  289          onLongPress: onLongPress,
  290          mouseCursor: mouseCursor,
  291          textTheme: textTheme,
  292          textColor: textColor,
  293          disabledTextColor: disabledTextColor,
  294          color: color,
  295          focusColor: focusColor,
  296          hoverColor: hoverColor,
  297          highlightColor: highlightColor,
  298          splashColor: splashColor,
  299          highlightElevation: highlightElevation,
  300          disabledBorderColor: disabledBorderColor,
  301          highlightedBorderColor: highlightedBorderColor,
  302          borderSide: borderSide,
  303          padding: padding,
  304          visualDensity: visualDensity,
  305          shape: shape,
  306          clipBehavior: clipBehavior,
  307          focusNode: focusNode,
  308          autofocus: autofocus,
  309          materialTapTargetSize: materialTapTargetSize,
  310          child: Row(
  311            mainAxisSize: MainAxisSize.min,
  312            children: <Widget>[
  313              icon,
  314              const SizedBox(width: 8.0),
  315              label,
  316            ],
  317          ),
  318        );
  319 }
  320 
  321 class _OutlineButton extends StatefulWidget {
  322   const _OutlineButton({
  323     Key key,
  324     @required this.onPressed,
  325     this.onLongPress,
  326     this.mouseCursor,
  327     this.brightness,
  328     this.textTheme,
  329     this.textColor,
  330     this.disabledTextColor,
  331     this.color,
  332     this.focusColor,
  333     this.hoverColor,
  334     this.highlightColor,
  335     this.splashColor,
  336     @required this.highlightElevation,
  337     this.borderSide,
  338     this.disabledBorderColor,
  339     @required this.highlightedBorderColor,
  340     this.padding,
  341     this.visualDensity,
  342     this.shape,
  343     this.clipBehavior = Clip.none,
  344     this.focusNode,
  345     this.autofocus = false,
  346     this.child,
  347     this.materialTapTargetSize,
  348   }) : assert(highlightElevation != null && highlightElevation >= 0.0),
  349        assert(highlightedBorderColor != null),
  350        assert(clipBehavior != null),
  351        assert(autofocus != null),
  352        super(key: key);
  353 
  354   final VoidCallback onPressed;
  355   final VoidCallback onLongPress;
  356   final MouseCursor mouseCursor;
  357   final Brightness brightness;
  358   final ButtonTextTheme textTheme;
  359   final Color textColor;
  360   final Color disabledTextColor;
  361   final Color color;
  362   final Color splashColor;
  363   final Color focusColor;
  364   final Color hoverColor;
  365   final Color highlightColor;
  366   final double highlightElevation;
  367   final BorderSide borderSide;
  368   final Color disabledBorderColor;
  369   final Color highlightedBorderColor;
  370   final EdgeInsetsGeometry padding;
  371   final VisualDensity visualDensity;
  372   final ShapeBorder shape;
  373   final Clip clipBehavior;
  374   final FocusNode focusNode;
  375   final bool autofocus;
  376   final Widget child;
  377   final MaterialTapTargetSize materialTapTargetSize;
  378 
  379   bool get enabled => onPressed != null || onLongPress != null;
  380 
  381   @override
  382   _OutlineButtonState createState() => _OutlineButtonState();
  383 }
  384 
  385 
  386 class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProviderStateMixin {
  387   AnimationController _controller;
  388   Animation<double> _fillAnimation;
  389   Animation<double> _elevationAnimation;
  390   bool _pressed = false;
  391 
  392   @override
  393   void initState() {
  394     super.initState();
  395 
  396     // When highlightElevation > 0.0, the Material widget animates its
  397     // shape (which includes the outline border) and elevation over
  398     // _kElevationDuration. When pressed, the button makes its fill
  399     // color opaque white first, and then sets its
  400     // highlightElevation. We can't change the elevation while the
  401     // button's fill is translucent, because the shadow fills the
  402     // interior of the button.
  403 
  404     _controller = AnimationController(
  405       duration: _kPressDuration,
  406       vsync: this,
  407     );
  408     _fillAnimation = CurvedAnimation(
  409       parent: _controller,
  410       curve: const Interval(0.0, 0.5,
  411         curve: Curves.fastOutSlowIn,
  412       ),
  413     );
  414     _elevationAnimation = CurvedAnimation(
  415       parent: _controller,
  416       curve: const Interval(0.5, 0.5),
  417       reverseCurve: const Interval(1.0, 1.0),
  418     );
  419   }
  420 
  421   @override
  422   void didUpdateWidget(_OutlineButton oldWidget) {
  423     super.didUpdateWidget(oldWidget);
  424     if (_pressed && !widget.enabled) {
  425       _pressed = false;
  426       _controller.reverse();
  427     }
  428   }
  429 
  430   void _handleHighlightChanged(bool value) {
  431     if (_pressed == value)
  432       return;
  433     setState(() {
  434       _pressed = value;
  435       if (value)
  436         _controller.forward();
  437       else
  438         _controller.reverse();
  439     });
  440   }
  441 
  442   @override
  443   void dispose() {
  444     _controller.dispose();
  445     super.dispose();
  446   }
  447 
  448   Color _getFillColor() {
  449     if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
  450       return Colors.transparent;
  451     final Color color = widget.color ?? Theme.of(context).canvasColor;
  452     final Tween<Color> colorTween = ColorTween(
  453       begin: color.withAlpha(0x00),
  454       end: color.withAlpha(0xFF),
  455     );
  456     return colorTween.evaluate(_fillAnimation);
  457   }
  458 
  459   Color get _outlineColor {
  460     // If outline color is a `MaterialStateProperty`, it will be used in all
  461     // states, otherwise we determine the outline color in the current state.
  462     if (widget.borderSide?.color is MaterialStateProperty<Color>)
  463       return widget.borderSide.color;
  464     if (!widget.enabled)
  465       return widget.disabledBorderColor;
  466     if (_pressed)
  467       return widget.highlightedBorderColor;
  468     return widget.borderSide?.color;
  469   }
  470 
  471   BorderSide _getOutline() {
  472     if (widget.borderSide?.style == BorderStyle.none)
  473       return widget.borderSide;
  474 
  475     final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
  476 
  477     return BorderSide(
  478       color: _outlineColor ?? themeColor,
  479       width: widget.borderSide?.width ?? 1.0,
  480     );
  481   }
  482 
  483   double _getHighlightElevation() {
  484     if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
  485       return 0.0;
  486     return Tween<double>(
  487       begin: 0.0,
  488       end: widget.highlightElevation,
  489     ).evaluate(_elevationAnimation);
  490   }
  491 
  492   @override
  493   Widget build(BuildContext context) {
  494     final ThemeData theme = Theme.of(context);
  495 
  496     return AnimatedBuilder(
  497       animation: _controller,
  498       builder: (BuildContext context, Widget child) {
  499         return RaisedButton(
  500           autofocus: widget.autofocus,
  501           textColor: widget.textColor,
  502           disabledTextColor: widget.disabledTextColor,
  503           color: _getFillColor(),
  504           splashColor: widget.splashColor,
  505           focusColor: widget.focusColor,
  506           hoverColor: widget.hoverColor,
  507           highlightColor: widget.highlightColor,
  508           disabledColor: Colors.transparent,
  509           onPressed: widget.onPressed,
  510           onLongPress: widget.onLongPress,
  511           mouseCursor: widget.mouseCursor,
  512           elevation: 0.0,
  513           disabledElevation: 0.0,
  514           focusElevation: 0.0,
  515           hoverElevation: 0.0,
  516           highlightElevation: _getHighlightElevation(),
  517           onHighlightChanged: _handleHighlightChanged,
  518           padding: widget.padding,
  519           visualDensity: widget.visualDensity ?? theme.visualDensity,
  520           shape: _OutlineBorder(
  521             shape: widget.shape,
  522             side: _getOutline(),
  523           ),
  524           clipBehavior: widget.clipBehavior,
  525           focusNode: widget.focusNode,
  526           animationDuration: _kElevationDuration,
  527           materialTapTargetSize: widget.materialTapTargetSize,
  528           child: widget.child,
  529         );
  530       },
  531     );
  532   }
  533 }
  534 
  535 // Render the button's outline border using using the OutlineButton's
  536 // border parameters and the button or buttonTheme's shape.
  537 class _OutlineBorder extends ShapeBorder implements MaterialStateProperty<ShapeBorder>{
  538   const _OutlineBorder({
  539     @required this.shape,
  540     @required this.side,
  541   }) : assert(shape != null),
  542        assert(side != null);
  543 
  544   final ShapeBorder shape;
  545   final BorderSide side;
  546 
  547   @override
  548   EdgeInsetsGeometry get dimensions {
  549     return EdgeInsets.all(side.width);
  550   }
  551 
  552   @override
  553   ShapeBorder scale(double t) {
  554     return _OutlineBorder(
  555       shape: shape.scale(t),
  556       side: side.scale(t),
  557     );
  558   }
  559 
  560   @override
  561   ShapeBorder lerpFrom(ShapeBorder a, double t) {
  562     assert(t != null);
  563     if (a is _OutlineBorder) {
  564       return _OutlineBorder(
  565         side: BorderSide.lerp(a.side, side, t),
  566         shape: ShapeBorder.lerp(a.shape, shape, t),
  567       );
  568     }
  569     return super.lerpFrom(a, t);
  570   }
  571 
  572   @override
  573   ShapeBorder lerpTo(ShapeBorder b, double t) {
  574     assert(t != null);
  575     if (b is _OutlineBorder) {
  576       return _OutlineBorder(
  577         side: BorderSide.lerp(side, b.side, t),
  578         shape: ShapeBorder.lerp(shape, b.shape, t),
  579       );
  580     }
  581     return super.lerpTo(b, t);
  582   }
  583 
  584   @override
  585   Path getInnerPath(Rect rect, { TextDirection textDirection }) {
  586     return shape.getInnerPath(rect.deflate(side.width), textDirection: textDirection);
  587   }
  588 
  589   @override
  590   Path getOuterPath(Rect rect, { TextDirection textDirection }) {
  591     return shape.getOuterPath(rect, textDirection: textDirection);
  592   }
  593 
  594   @override
  595   void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
  596     switch (side.style) {
  597       case BorderStyle.none:
  598         break;
  599       case BorderStyle.solid:
  600         canvas.drawPath(shape.getOuterPath(rect, textDirection: textDirection), side.toPaint());
  601     }
  602   }
  603 
  604   @override
  605   bool operator ==(Object other) {
  606     if (identical(this, other))
  607       return true;
  608     if (other.runtimeType != runtimeType)
  609       return false;
  610     return other is _OutlineBorder
  611         && other.side == side
  612         && other.shape == shape;
  613   }
  614 
  615   @override
  616   int get hashCode => hashValues(side, shape);
  617 
  618   @override
  619   ShapeBorder resolve(Set<MaterialState> states) {
  620     return _OutlineBorder(
  621       shape: shape,
  622       side: side.copyWith(color: MaterialStateProperty.resolveAs<Color>(side.color, states),
  623     ));
  624   }
  625 }