button.dart 7.14 KB
Newer Older
1 2 3 4
// Copyright 2017 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.

5 6
import 'dart:async';

7 8 9
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

xster's avatar
xster committed
10 11
import 'colors.dart';

Adam Barth's avatar
Adam Barth committed
12 13 14
const Color _kDisabledBackground = const Color(0xFFA9A9A9);
const Color _kDisabledForeground = const Color(0xFFC4C4C4);

15 16 17
const TextStyle _kButtonTextStyle = const TextStyle(
  fontFamily: '.SF UI Text',
  inherit: false,
18 19 20
  fontSize: 17.5,
  letterSpacing: -0.24,
  fontWeight: FontWeight.w400,
xster's avatar
xster committed
21
  color: CupertinoColors.activeBlue,
22 23 24 25
  textBaseline: TextBaseline.alphabetic,
);

final TextStyle _kDisabledButtonTextStyle = _kButtonTextStyle.copyWith(
Adam Barth's avatar
Adam Barth committed
26
  color: _kDisabledForeground,
27 28 29
);

final TextStyle _kBackgroundButtonTextStyle = _kButtonTextStyle.copyWith(
xster's avatar
xster committed
30
  color: CupertinoColors.white,
31 32 33
);

const EdgeInsets _kButtonPadding = const EdgeInsets.all(16.0);
34
const EdgeInsets _kBackgroundButtonPadding = const EdgeInsets.symmetric(
35
  vertical: 14.0,
36 37
  horizontal: 64.0,
);
38

Adam Barth's avatar
Adam Barth committed
39
/// An iOS-style button.
40 41 42 43 44 45
///
/// Takes in a text or an icon that fades out and in on touch. May optionally have a
/// background.
///
/// See also:
///
46
///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/buttons/>
47
class CupertinoButton extends StatefulWidget {
Adam Barth's avatar
Adam Barth committed
48
  /// Creates an iOS-style button.
49
  const CupertinoButton({
50 51 52
    @required this.child,
    this.padding,
    this.color,
53
    this.minSize: 44.0,
54
    this.pressedOpacity: 0.1,
xster's avatar
xster committed
55
    this.borderRadius: const BorderRadius.all(const Radius.circular(8.0)),
56
    @required this.onPressed,
xster's avatar
xster committed
57
  }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0));
58 59 60 61 62 63 64 65 66

  /// The widget below this widget in the tree.
  ///
  /// Typically a [Text] widget.
  final Widget child;

  /// The amount of space to surround the child inside the bounds of the button.
  ///
  /// Defaults to 16.0 pixels.
67
  final EdgeInsetsGeometry padding;
68 69 70 71 72 73 74 75 76 77 78

  /// The color of the button's background.
  ///
  /// Defaults to null which produces a button with no background or border.
  final Color color;

  /// The callback that is called when the button is tapped or otherwise activated.
  ///
  /// If this is set to null, the button will be disabled.
  final VoidCallback onPressed;

79 80 81 82 83 84 85
  /// Minimum size of the button.
  ///
  /// Defaults to 44.0 which the iOS Human Interface Guideline recommends as the
  /// minimum tappable area
  ///
  /// See also:
  ///
86
  /// * <https://developer.apple.com/ios/human-interface-guidelines/visual-design/adaptivity-and-layout/>
87 88
  final double minSize;

89 90 91
  /// The opacity that the button will fade to when it is pressed.
  /// The button will have an opacity of 1.0 when it is not pressed.
  ///
xster's avatar
xster committed
92 93
  /// This defaults to 0.1. If null, opacity will not change on pressed if using
  /// your own custom effects is desired.
94 95
  final double pressedOpacity;

xster's avatar
xster committed
96 97 98 99 100
  /// The radius of the button's corners when it has a background color.
  ///
  /// Defaults to round corners of 8 logical pixels.
  final BorderRadius borderRadius;

101 102 103 104 105 106 107 108
  /// Whether the button is enabled or disabled. Buttons are disabled by default. To
  /// enable a button, set its [onPressed] property to a non-null value.
  bool get enabled => onPressed != null;

  @override
  _CupertinoButtonState createState() => new _CupertinoButtonState();

  @override
109 110 111
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
112 113 114 115 116 117
  }
}

class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProviderStateMixin {
  // Eyeballed values. Feel free to tweak.
  static const Duration kFadeOutDuration = const Duration(milliseconds: 10);
118
  static const Duration kFadeInDuration = const Duration(milliseconds: 100);
119
  Tween<double> _opacityTween;
120 121 122

  AnimationController _animationController;

123 124 125
  void _setTween() {
    _opacityTween = new Tween<double>(
      begin: 1.0,
xster's avatar
xster committed
126
      end: widget.pressedOpacity ?? 1.0,
127 128 129
    );
  }

130 131 132 133 134
  @override
  void initState() {
    super.initState();
    _animationController = new AnimationController(
      duration: const Duration(milliseconds: 200),
135
      value: 0.0,
136 137
      vsync: this,
    );
138
    _setTween();
139 140 141 142 143 144 145 146 147
  }

  @override
  void dispose() {
    _animationController.dispose();
    _animationController = null;
    super.dispose();
  }

148
  @override
149 150
  void didUpdateWidget(CupertinoButton old) {
    super.didUpdateWidget(old);
151 152 153
    _setTween();
  }

154 155 156 157 158 159 160 161 162 163 164 165 166 167
  bool _buttonHeldDown = false;

  void _handleTapDown(TapDownDetails event) {
    if (!_buttonHeldDown) {
      _buttonHeldDown = true;
      _animate();
    }
  }

  void _handleTapUp(TapUpDetails event) {
    if (_buttonHeldDown) {
      _buttonHeldDown = false;
      _animate();
    }
168 169
  }

170 171 172 173 174
  void _handleTapCancel() {
    if (_buttonHeldDown) {
      _buttonHeldDown = false;
      _animate();
    }
175 176
  }

177 178 179 180 181 182 183 184 185 186 187
  void _animate() {
    if (_animationController.isAnimating)
      return;
    final bool wasHeldDown = _buttonHeldDown;
    final Future<Null> ticker = _buttonHeldDown
        ? _animationController.animateTo(1.0, duration: kFadeOutDuration)
        : _animationController.animateTo(0.0, duration: kFadeInDuration);
    ticker.then((Null value) {
      if (mounted && wasHeldDown != _buttonHeldDown)
        _animate();
    });
188 189 190 191
  }

  @override
  Widget build(BuildContext context) {
192 193
    final bool enabled = widget.enabled;
    final Color backgroundColor = widget.color;
194

195
    return new GestureDetector(
196
      behavior: HitTestBehavior.opaque,
197 198 199 200
      onTapDown: enabled ? _handleTapDown : null,
      onTapUp: enabled ? _handleTapUp : null,
      onTapCancel: enabled ? _handleTapCancel : null,
      onTap: widget.onPressed,
201 202 203 204
      child: new Semantics(
        button: true,
        child: new ConstrainedBox(
          constraints: widget.minSize == null
205 206 207 208 209
            ? const BoxConstraints()
            : new BoxConstraints(
              minWidth: widget.minSize,
              minHeight: widget.minSize,
            ),
210 211
          child: new FadeTransition(
            opacity: _opacityTween.animate(new CurvedAnimation(
212 213
              parent: _animationController,
              curve: Curves.decelerate,
214 215 216 217 218
            )),
            child: new DecoratedBox(
              decoration: new BoxDecoration(
                borderRadius: widget.borderRadius,
                color: backgroundColor != null && !enabled
219 220
                  ? _kDisabledBackground
                  : backgroundColor,
221 222 223 224 225 226 227 228 229 230
              ),
              child: new Padding(
                padding: widget.padding ?? (backgroundColor != null
                  ? _kBackgroundButtonPadding
                  : _kButtonPadding),
                child: new Center(
                  widthFactor: 1.0,
                  heightFactor: 1.0,
                  child: new DefaultTextStyle(
                    style: backgroundColor != null
231 232 233 234
                      ? _kBackgroundButtonTextStyle
                      : enabled
                        ? _kButtonTextStyle
                        : _kDisabledButtonTextStyle,
235 236
                    child: widget.child,
                  ),
237 238 239 240 241 242 243 244 245
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}