33// found in the LICENSE file.
44
55import 'package:flutter/foundation.dart' ;
6+ import 'package:flutter/rendering.dart' ;
67import 'package:flutter/widgets.dart' ;
78
89import 'button_theme.dart' ;
@@ -11,6 +12,7 @@ import 'constants.dart';
1112import 'ink_well.dart' ;
1213import 'material.dart' ;
1314import 'theme.dart' ;
15+ import 'theme_data.dart' ;
1416
1517/// Creates a button based on [Semantics] , [Material] , and [InkWell]
1618/// widgets.
@@ -38,13 +40,14 @@ class RawMaterialButton extends StatefulWidget {
3840 this .elevation = 2.0 ,
3941 this .highlightElevation = 8.0 ,
4042 this .disabledElevation = 0.0 ,
41- this .outerPadding,
4243 this .padding = EdgeInsets .zero,
4344 this .constraints = const BoxConstraints (minWidth: 88.0 , minHeight: 36.0 ),
4445 this .shape = const RoundedRectangleBorder (),
4546 this .animationDuration = kThemeChangeDuration,
47+ MaterialTapTargetSize materialTapTargetSize,
4648 this .child,
47- }) : assert (shape != null ),
49+ }) : this .materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize .padded,
50+ assert (shape != null ),
4851 assert (elevation != null ),
4952 assert (highlightElevation != null ),
5053 assert (disabledElevation != null ),
@@ -58,10 +61,6 @@ class RawMaterialButton extends StatefulWidget {
5861 /// If this is set to null, the button will be disabled, see [enabled] .
5962 final VoidCallback onPressed;
6063
61- /// Padding to increase the size of the gesture detector which doesn't
62- /// increase the visible material of the button.
63- final EdgeInsets outerPadding;
64-
6564 /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
6665 /// callback.
6766 final ValueChanged <bool > onHighlightChanged;
@@ -138,6 +137,15 @@ class RawMaterialButton extends StatefulWidget {
138137 /// property to a non-null value.
139138 bool get enabled => onPressed != null ;
140139
140+ /// Configures the minimum size of the tap target.
141+ ///
142+ /// Defaults to [MaterialTapTargetSize.padded] .
143+ ///
144+ /// See also:
145+ ///
146+ /// * [MaterialTapTargetSize] , for a description of how this affects tap targets.
147+ final MaterialTapTargetSize materialTapTargetSize;
148+
141149 @override
142150 _RawMaterialButtonState createState () => new _RawMaterialButtonState ();
143151}
@@ -186,18 +194,23 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
186194 ),
187195 ),
188196 );
189-
190- if (widget.outerPadding != null ) {
191- result = new GestureDetector (
192- behavior: HitTestBehavior .translucent,
193- excludeFromSemantics: true ,
194- onTap: widget.onPressed,
195- child: new Padding (
196- padding: widget.outerPadding,
197- child: result
198- ),
199- );
197+ BoxConstraints constraints;
198+ switch (widget.materialTapTargetSize) {
199+ case MaterialTapTargetSize .padded:
200+ constraints = const BoxConstraints (minWidth: 48.0 , minHeight: 48.0 );
201+ break ;
202+ case MaterialTapTargetSize .shrinkWrap:
203+ constraints = const BoxConstraints ();
204+ break ;
200205 }
206+ result = new _ButtonRedirectingHitDetectionWidget (
207+ constraints: constraints,
208+ child: new Center (
209+ child: result,
210+ widthFactor: 1.0 ,
211+ heightFactor: 1.0 ,
212+ ),
213+ );
201214
202215 return new Semantics (
203216 container: true ,
@@ -248,6 +261,7 @@ class MaterialButton extends StatelessWidget {
248261 this .minWidth,
249262 this .height,
250263 this .padding,
264+ this .materialTapTargetSize,
251265 @required this .onPressed,
252266 this .child
253267 }) : super (key: key);
@@ -353,6 +367,15 @@ class MaterialButton extends StatelessWidget {
353367 /// {@macro flutter.widgets.child}
354368 final Widget child;
355369
370+ /// Configures the minimum size of the tap target.
371+ ///
372+ /// Defaults to [ThemeData.materialTapTargetSize] .
373+ ///
374+ /// See also:
375+ ///
376+ /// * [MaterialTapTargetSize] , for a description of how this affects tap targets.
377+ final MaterialTapTargetSize materialTapTargetSize;
378+
356379 /// Whether the button is enabled or disabled. Buttons are disabled by default. To
357380 /// enable a button, set its [onPressed] property to a non-null value.
358381 bool get enabled => onPressed != null ;
@@ -412,6 +435,7 @@ class MaterialButton extends StatelessWidget {
412435 ),
413436 shape: buttonTheme.shape,
414437 child: child,
438+ materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
415439 );
416440 }
417441
@@ -421,3 +445,38 @@ class MaterialButton extends StatelessWidget {
421445 properties.add (new FlagProperty ('enabled' , value: enabled, ifFalse: 'disabled' ));
422446 }
423447}
448+
449+ /// Redirects the position passed to [RenderBox.hitTest] to the center of the widget.
450+ ///
451+ /// The primary purpose of this widget is to allow padding around [Material] widgets
452+ /// to trigger the child ink feature without increasing the size of the material.
453+ class _ButtonRedirectingHitDetectionWidget extends SingleChildRenderObjectWidget {
454+ const _ButtonRedirectingHitDetectionWidget ({
455+ Key key,
456+ Widget child,
457+ this .constraints
458+ }) : super (key: key, child: child);
459+
460+ final BoxConstraints constraints;
461+
462+ @override
463+ RenderObject createRenderObject (BuildContext context) {
464+ return new _RenderButtonRedirectingHitDetection (constraints);
465+ }
466+
467+ @override
468+ void updateRenderObject (BuildContext context, covariant _RenderButtonRedirectingHitDetection renderObject) {
469+ renderObject.additionalConstraints = constraints;
470+ }
471+ }
472+
473+ class _RenderButtonRedirectingHitDetection extends RenderConstrainedBox {
474+ _RenderButtonRedirectingHitDetection (BoxConstraints additionalConstraints) : super (additionalConstraints: additionalConstraints);
475+
476+ @override
477+ bool hitTest (HitTestResult result, {Offset position}) {
478+ if (! size.contains (position))
479+ return false ;
480+ return child.hitTest (result, position: size.center (Offset .zero));
481+ }
482+ }
0 commit comments