button.dart 6.3 KB
Newer Older
1 2 3 4 5 6 7
// 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.

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Adam Barth's avatar
Adam Barth committed
8 9 10 11 12 13
// TODO(xster): move this to a common Cupertino color palette with the next yak.
const Color _kBlue = const Color(0xFF007AFF);
const Color _kWhite = const Color(0xFFFFFFFF);
const Color _kDisabledBackground = const Color(0xFFA9A9A9);
const Color _kDisabledForeground = const Color(0xFFC4C4C4);

14 15 16 17 18
const TextStyle _kButtonTextStyle = const TextStyle(
  fontFamily: '.SF UI Text',
  inherit: false,
  fontSize: 15.0,
  fontWeight: FontWeight.normal,
Adam Barth's avatar
Adam Barth committed
19
  color: _kBlue,
20 21 22 23
  textBaseline: TextBaseline.alphabetic,
);

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

final TextStyle _kBackgroundButtonTextStyle = _kButtonTextStyle.copyWith(
Adam Barth's avatar
Adam Barth committed
28
  color: _kWhite,
29 30 31 32
);

const EdgeInsets _kButtonPadding = const EdgeInsets.all(16.0);
const EdgeInsets _kBackgroundButtonPadding =
33
    const EdgeInsets.symmetric(vertical: 16.0, horizontal: 64.0);
34

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

  /// 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.
  final EdgeInsets padding;

  /// 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;

74 75 76 77 78 79 80 81 82 83
  /// Minimum size of the button.
  ///
  /// Defaults to 44.0 which the iOS Human Interface Guideline recommends as the
  /// minimum tappable area
  ///
  /// See also:
  ///
  /// * <https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/>
  final double minSize;

84 85 86 87 88 89
  /// 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.
  ///
  /// This defaults to 0.1.
  final double pressedOpacity;

90 91 92 93 94 95 96 97 98 99 100 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
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (!enabled)
      description.add('disabled');
  }
}

class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProviderStateMixin {
  // Eyeballed values. Feel free to tweak.
  static const Duration kFadeOutDuration = const Duration(milliseconds: 10);
  static const Duration kFadeInDuration = const Duration(milliseconds: 350);
109
  Tween<double> _opacityTween;
110 111 112

  AnimationController _animationController;

113 114 115
  void _setTween() {
    _opacityTween = new Tween<double>(
      begin: 1.0,
116
      end: widget.pressedOpacity,
117 118 119
    );
  }

120 121 122 123 124
  @override
  void initState() {
    super.initState();
    _animationController = new AnimationController(
      duration: const Duration(milliseconds: 200),
125
      value: 0.0,
126 127
      vsync: this,
    );
128
    _setTween();
129 130 131 132 133 134 135 136 137
  }

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

138
  @override
139 140
  void didUpdateWidget(CupertinoButton old) {
    super.didUpdateWidget(old);
141 142 143
    _setTween();
  }

144
  void _handleTapDown(PointerDownEvent event) {
145
    _animationController.animateTo(1.0, duration: kFadeOutDuration);
146 147 148
  }

  void _handleTapUp(PointerUpEvent event) {
149
    _animationController.animateTo(0.0, duration: kFadeInDuration);
150 151 152
  }

  void _handleTapCancel(PointerCancelEvent event) {
153
    _animationController.animateTo(0.0, duration: kFadeInDuration);
154 155 156 157
  }

  @override
  Widget build(BuildContext context) {
158 159
    final bool enabled = widget.enabled;
    final Color backgroundColor = widget.color;
160 161 162 163 164 165

    return new Listener(
      onPointerDown: enabled ? _handleTapDown : null,
      onPointerUp: enabled ? _handleTapUp : null,
      onPointerCancel: enabled ? _handleTapCancel : null,
      child: new GestureDetector(
166
        onTap: widget.onPressed,
167
        child: new ConstrainedBox(
168
          constraints: new BoxConstraints(
169 170
            minWidth: widget.minSize,
            minHeight: widget.minSize,
171
          ),
172
          child: new FadeTransition(
173
            opacity: _opacityTween.animate(new CurvedAnimation(
174 175
              parent: _animationController,
              curve: Curves.decelerate,
176
            )),
177 178 179
            child: new DecoratedBox(
              decoration: new BoxDecoration(
                borderRadius: const BorderRadius.all(const Radius.circular(8.0)),
180
                color: backgroundColor != null && !enabled
Adam Barth's avatar
Adam Barth committed
181
                    ? _kDisabledBackground
182 183 184
                    : backgroundColor,
              ),
              child: new Padding(
185 186
                padding: widget.padding != null
                    ? widget.padding
187 188 189 190 191 192 193 194 195 196 197 198
                    : backgroundColor != null
                        ? _kBackgroundButtonPadding
                        : _kButtonPadding,
                child: new Center(
                  widthFactor: 1.0,
                  heightFactor: 1.0,
                  child: new DefaultTextStyle(
                    style: backgroundColor != null
                        ? _kBackgroundButtonTextStyle
                        : enabled
                            ? _kButtonTextStyle
                            : _kDisabledButtonTextStyle,
199
                    child: widget.child,
200 201 202 203 204 205 206 207 208 209
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}