Unverified Commit c4e84380 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added Material OutlineButton (#14939)

parent da24ad0b
...@@ -65,6 +65,7 @@ export 'src/material/list_tile.dart'; ...@@ -65,6 +65,7 @@ export 'src/material/list_tile.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/material_localizations.dart'; export 'src/material/material_localizations.dart';
export 'src/material/mergeable_material.dart'; export 'src/material/mergeable_material.dart';
export 'src/material/outline_button.dart';
export 'src/material/page.dart'; export 'src/material/page.dart';
export 'src/material/paginated_data_table.dart'; export 'src/material/paginated_data_table.dart';
export 'src/material/popup_menu.dart'; export 'src/material/popup_menu.dart';
......
...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -29,6 +30,7 @@ class RawMaterialButton extends StatefulWidget { ...@@ -29,6 +30,7 @@ class RawMaterialButton extends StatefulWidget {
const RawMaterialButton({ const RawMaterialButton({
Key key, Key key,
@required this.onPressed, @required this.onPressed,
this.onHighlightChanged,
this.textStyle, this.textStyle,
this.fillColor, this.fillColor,
this.highlightColor, this.highlightColor,
...@@ -39,6 +41,7 @@ class RawMaterialButton extends StatefulWidget { ...@@ -39,6 +41,7 @@ class RawMaterialButton extends StatefulWidget {
this.padding: EdgeInsets.zero, this.padding: EdgeInsets.zero,
this.constraints: const BoxConstraints(minWidth: 88.0, minHeight: 36.0), this.constraints: const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
this.shape: const RoundedRectangleBorder(), this.shape: const RoundedRectangleBorder(),
this.animationDuration: kThemeChangeDuration,
this.child, this.child,
}) : assert(shape != null), }) : assert(shape != null),
assert(elevation != null), assert(elevation != null),
...@@ -46,6 +49,7 @@ class RawMaterialButton extends StatefulWidget { ...@@ -46,6 +49,7 @@ class RawMaterialButton extends StatefulWidget {
assert(disabledElevation != null), assert(disabledElevation != null),
assert(padding != null), assert(padding != null),
assert(constraints != null), assert(constraints != null),
assert(animationDuration != null),
super(key: key); super(key: key);
/// Called when the button is tapped or otherwise activated. /// Called when the button is tapped or otherwise activated.
...@@ -53,6 +57,10 @@ class RawMaterialButton extends StatefulWidget { ...@@ -53,6 +57,10 @@ class RawMaterialButton extends StatefulWidget {
/// If this is set to null, the button will be disabled, see [enabled]. /// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed; final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the default text style, with [Material.textStyle], for the /// Defines the default text style, with [Material.textStyle], for the
/// button's [child]. /// button's [child].
final TextStyle textStyle; final TextStyle textStyle;
...@@ -111,6 +119,11 @@ class RawMaterialButton extends StatefulWidget { ...@@ -111,6 +119,11 @@ class RawMaterialButton extends StatefulWidget {
/// button has an elevation, then its drop shadow is defined by this shape. /// button has an elevation, then its drop shadow is defined by this shape.
final ShapeBorder shape; final ShapeBorder shape;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
/// Typically the button's label. /// Typically the button's label.
final Widget child; final Widget child;
...@@ -129,6 +142,8 @@ class _RawMaterialButtonState extends State<RawMaterialButton> { ...@@ -129,6 +142,8 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
void _handleHighlightChanged(bool value) { void _handleHighlightChanged(bool value) {
setState(() { setState(() {
_highlight = value; _highlight = value;
if (widget.onHighlightChanged != null)
widget.onHighlightChanged(value);
}); });
} }
...@@ -150,6 +165,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> { ...@@ -150,6 +165,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
shape: widget.shape, shape: widget.shape,
color: widget.fillColor, color: widget.fillColor,
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button, type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
animationDuration: widget.animationDuration,
child: new InkWell( child: new InkWell(
onHighlightChanged: _handleHighlightChanged, onHighlightChanged: _handleHighlightChanged,
splashColor: widget.splashColor, splashColor: widget.splashColor,
......
...@@ -34,7 +34,8 @@ import 'theme.dart'; ...@@ -34,7 +34,8 @@ import 'theme.dart';
/// trying to change the button's [color] and it is not having any effect, check /// trying to change the button's [color] and it is not having any effect, check
/// that you are passing a non-null [onPressed] handler. /// that you are passing a non-null [onPressed] handler.
/// ///
/// Flat buttons will expand to fit the child widget, if necessary. /// Flat buttons have a minimum size of 88.0 by 36.0 which can be overidden
/// with [ButtonTheme].
/// ///
/// See also: /// See also:
/// ///
...@@ -43,13 +44,14 @@ import 'theme.dart'; ...@@ -43,13 +44,14 @@ import 'theme.dart';
/// * [SimpleDialogOption], which is used in [SimpleDialog]s. /// * [SimpleDialogOption], which is used in [SimpleDialog]s.
/// * [IconButton], to create buttons that just contain icons. /// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
//// * [RawMaterialButton], the widget this widget is based on. /// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html> /// * <https://material.google.com/components/buttons.html>
class FlatButton extends StatelessWidget { class FlatButton extends StatelessWidget {
/// Create a simple text button. /// Create a simple text button.
const FlatButton({ const FlatButton({
Key key, Key key,
@required this.onPressed, @required this.onPressed,
this.onHighlightChanged,
this.textTheme, this.textTheme,
this.textColor, this.textColor,
this.disabledTextColor, this.disabledTextColor,
...@@ -73,6 +75,7 @@ class FlatButton extends StatelessWidget { ...@@ -73,6 +75,7 @@ class FlatButton extends StatelessWidget {
FlatButton.icon({ FlatButton.icon({
Key key, Key key,
@required this.onPressed, @required this.onPressed,
this.onHighlightChanged,
this.textTheme, this.textTheme,
this.textColor, this.textColor,
this.disabledTextColor, this.disabledTextColor,
...@@ -102,6 +105,10 @@ class FlatButton extends StatelessWidget { ...@@ -102,6 +105,10 @@ class FlatButton extends StatelessWidget {
/// If this is set to null, the button will be disabled, see [enabled]. /// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed; final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum /// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape. /// size, internal padding, and shape.
/// ///
...@@ -276,6 +283,7 @@ class FlatButton extends StatelessWidget { ...@@ -276,6 +283,7 @@ class FlatButton extends StatelessWidget {
return new RawMaterialButton( return new RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
fillColor: fillColor, fillColor: fillColor,
textStyle: theme.textTheme.button.copyWith(color: textColor), textStyle: theme.textTheme.button.copyWith(color: textColor),
highlightColor: _getHighlightColor(theme, buttonTheme), highlightColor: _getHighlightColor(theme, buttonTheme),
......
...@@ -88,7 +88,7 @@ abstract class MaterialInkController { ...@@ -88,7 +88,7 @@ abstract class MaterialInkController {
/// The Material widget is responsible for: /// The Material widget is responsible for:
/// ///
/// 1. Clipping: Material clips its widget sub-tree to the shape specified by /// 1. Clipping: Material clips its widget sub-tree to the shape specified by
/// [type] and [borderRadius]. /// [shape], [type], and [borderRadius].
/// 2. Elevation: Material elevates its widget sub-tree on the Z axis by /// 2. Elevation: Material elevates its widget sub-tree on the Z axis by
/// [elevation] pixels, and draws the appropriate shadow. /// [elevation] pixels, and draws the appropriate shadow.
/// 3. Ink effects: Material shows ink effects implemented by [InkFeature]s /// 3. Ink effects: Material shows ink effects implemented by [InkFeature]s
...@@ -108,14 +108,15 @@ abstract class MaterialInkController { ...@@ -108,14 +108,15 @@ abstract class MaterialInkController {
/// ///
/// In general, the features of a [Material] should not change over time (e.g. a /// In general, the features of a [Material] should not change over time (e.g. a
/// [Material] should not change its [color], [shadowColor] or [type]). /// [Material] should not change its [color], [shadowColor] or [type]).
/// Changes to [elevation] and [shadowColor] are animated. Changes to [shape] are /// Changes to [elevation] and [shadowColor] are animated for [animationDuration].
/// animated if [type] is not [MaterialType.transparency] and [ShapeBorder.lerp] /// Changes to [shape] are animated if [type] is not [MaterialType.transparency]
/// between the previous and next [shape] values is supported. /// and [ShapeBorder.lerp] between the previous and next [shape] values is
/// supported. Shape changes are also animated for [animationDuration].
/// ///
/// ///
/// ## Shape /// ## Shape
/// ///
/// The shape for material is determined by [type] and [borderRadius]. /// The shape for material is determined by [shape], [type], and [borderRadius].
/// ///
/// - If [shape] is non null, it determines the shape. /// - If [shape] is non null, it determines the shape.
/// - If [shape] is null and [borderRadius] is non null, the shape is a /// - If [shape] is null and [borderRadius] is non null, the shape is a
...@@ -153,9 +154,10 @@ abstract class MaterialInkController { ...@@ -153,9 +154,10 @@ abstract class MaterialInkController {
class Material extends StatefulWidget { class Material extends StatefulWidget {
/// Creates a piece of material. /// Creates a piece of material.
/// ///
/// The [type], [elevation] and [shadowColor] arguments must not be null. /// The [type], [elevation], [shadowColor], and [animationDuration] arguments
/// must not be null.
/// ///
/// If a [shape] is specified, then the [borderRadius] property must not be /// If a [shape] is specified, then the [borderRadius] property must be
/// null and the [type] property must not be [MaterialType.circle]. If the /// null and the [type] property must not be [MaterialType.circle]. If the
/// [borderRadius] is specified, then the [type] property must not be /// [borderRadius] is specified, then the [type] property must not be
/// [MaterialType.circle]. In both cases, these restrictions are intended to /// [MaterialType.circle]. In both cases, these restrictions are intended to
...@@ -169,11 +171,13 @@ class Material extends StatefulWidget { ...@@ -169,11 +171,13 @@ class Material extends StatefulWidget {
this.textStyle, this.textStyle,
this.borderRadius, this.borderRadius,
this.shape, this.shape,
this.animationDuration: kThemeChangeDuration,
this.child, this.child,
}) : assert(type != null), }) : assert(type != null),
assert(elevation != null), assert(elevation != null),
assert(shadowColor != null), assert(shadowColor != null),
assert(!(shape != null && borderRadius != null)), assert(!(shape != null && borderRadius != null)),
assert(animationDuration != null),
assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))), assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))),
super(key: key); super(key: key);
...@@ -194,7 +198,7 @@ class Material extends StatefulWidget { ...@@ -194,7 +198,7 @@ class Material extends StatefulWidget {
/// widget conceptually defines an independent printed piece of material. /// widget conceptually defines an independent printed piece of material.
/// ///
/// Defaults to 0. Changing this value will cause the shadow to animate over /// Defaults to 0. Changing this value will cause the shadow to animate over
/// [kThemeChangeDuration]. /// [animationDuration].
final double elevation; final double elevation;
/// The color to paint the material. /// The color to paint the material.
...@@ -222,6 +226,12 @@ class Material extends StatefulWidget { ...@@ -222,6 +226,12 @@ class Material extends StatefulWidget {
/// zero. /// zero.
final ShapeBorder shape; final ShapeBorder shape;
/// Defines the duration of animated changes for [shape], [elevation],
/// and [shadowColor].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
/// If non-null, the corners of this box are rounded by this [BorderRadius]. /// If non-null, the corners of this box are rounded by this [BorderRadius].
/// Otherwise, the corners specified for the current [type] of material are /// Otherwise, the corners specified for the current [type] of material are
/// used. /// used.
...@@ -287,7 +297,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -287,7 +297,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
if (contents != null) { if (contents != null) {
contents = new AnimatedDefaultTextStyle( contents = new AnimatedDefaultTextStyle(
style: widget.textStyle ?? Theme.of(context).textTheme.body1, style: widget.textStyle ?? Theme.of(context).textTheme.body1,
duration: kThemeChangeDuration, duration: widget.animationDuration,
child: contents child: contents
); );
} }
...@@ -317,7 +327,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -317,7 +327,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
if (widget.type == MaterialType.canvas && widget.shape == null && widget.borderRadius == null) { if (widget.type == MaterialType.canvas && widget.shape == null && widget.borderRadius == null) {
return new AnimatedPhysicalModel( return new AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
duration: kThemeChangeDuration, duration: widget.animationDuration,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.zero, borderRadius: BorderRadius.zero,
elevation: widget.elevation, elevation: widget.elevation,
...@@ -335,7 +345,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -335,7 +345,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
return new _MaterialInterior( return new _MaterialInterior(
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
duration: kThemeChangeDuration, duration: widget.animationDuration,
shape: shape, shape: shape,
elevation: widget.elevation, elevation: widget.elevation,
color: backgroundColor, color: backgroundColor,
......
This diff is collapsed.
...@@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
import 'button.dart'; import 'button.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart';
import 'theme.dart'; import 'theme.dart';
/// A material design "raised button". /// A material design "raised button".
...@@ -27,7 +28,8 @@ import 'theme.dart'; ...@@ -27,7 +28,8 @@ import 'theme.dart';
/// If you want an ink-splash effect for taps, but don't want to use a button, /// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly. /// consider using [InkWell] directly.
/// ///
/// Raised buttons will expand to fit the child widget, if necessary. /// Raised buttons have a minimum size of 88.0 by 36.0 which can be overidden
/// with [ButtonTheme].
/// ///
/// See also: /// See also:
/// ///
...@@ -36,7 +38,7 @@ import 'theme.dart'; ...@@ -36,7 +38,7 @@ import 'theme.dart';
/// * [FloatingActionButton], the round button in material applications. /// * [FloatingActionButton], the round button in material applications.
/// * [IconButton], to create buttons that just contain icons. /// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
//// * [RawMaterialButton], the widget this widget is based on. /// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html> /// * <https://material.google.com/components/buttons.html>
class RaisedButton extends StatelessWidget { class RaisedButton extends StatelessWidget {
/// Create a filled button. /// Create a filled button.
...@@ -46,6 +48,7 @@ class RaisedButton extends StatelessWidget { ...@@ -46,6 +48,7 @@ class RaisedButton extends StatelessWidget {
const RaisedButton({ const RaisedButton({
Key key, Key key,
@required this.onPressed, @required this.onPressed,
this.onHighlightChanged,
this.textTheme, this.textTheme,
this.textColor, this.textColor,
this.disabledTextColor, this.disabledTextColor,
...@@ -59,10 +62,12 @@ class RaisedButton extends StatelessWidget { ...@@ -59,10 +62,12 @@ class RaisedButton extends StatelessWidget {
this.disabledElevation: 0.0, this.disabledElevation: 0.0,
this.padding, this.padding,
this.shape, this.shape,
this.animationDuration: kThemeChangeDuration,
this.child, this.child,
}) : assert(elevation != null), }) : assert(elevation != null),
assert(highlightElevation != null), assert(highlightElevation != null),
assert(disabledElevation != null), assert(disabledElevation != null),
assert(animationDuration != null),
super(key: key); super(key: key);
/// Create a filled button from a pair of widgets that serve as the button's /// Create a filled button from a pair of widgets that serve as the button's
...@@ -76,6 +81,7 @@ class RaisedButton extends StatelessWidget { ...@@ -76,6 +81,7 @@ class RaisedButton extends StatelessWidget {
RaisedButton.icon({ RaisedButton.icon({
Key key, Key key,
@required this.onPressed, @required this.onPressed,
this.onHighlightChanged,
this.textTheme, this.textTheme,
this.textColor, this.textColor,
this.disabledTextColor, this.disabledTextColor,
...@@ -88,6 +94,7 @@ class RaisedButton extends StatelessWidget { ...@@ -88,6 +94,7 @@ class RaisedButton extends StatelessWidget {
this.highlightElevation: 8.0, this.highlightElevation: 8.0,
this.disabledElevation: 0.0, this.disabledElevation: 0.0,
this.shape, this.shape,
this.animationDuration: kThemeChangeDuration,
@required Widget icon, @required Widget icon,
@required Widget label, @required Widget label,
}) : assert(elevation != null), }) : assert(elevation != null),
...@@ -95,6 +102,7 @@ class RaisedButton extends StatelessWidget { ...@@ -95,6 +102,7 @@ class RaisedButton extends StatelessWidget {
assert(disabledElevation != null), assert(disabledElevation != null),
assert(icon != null), assert(icon != null),
assert(label != null), assert(label != null),
assert(animationDuration != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0), padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = new Row( child = new Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
...@@ -111,6 +119,10 @@ class RaisedButton extends StatelessWidget { ...@@ -111,6 +119,10 @@ class RaisedButton extends StatelessWidget {
/// If this is set to null, the button will be disabled, see [enabled]. /// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed; final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum /// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape. /// size, internal padding, and shape.
/// ///
...@@ -272,6 +284,11 @@ class RaisedButton extends StatelessWidget { ...@@ -272,6 +284,11 @@ class RaisedButton extends StatelessWidget {
/// shape as well. /// shape as well.
final ShapeBorder shape; final ShapeBorder shape;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
Brightness _getBrightness(ThemeData theme) { Brightness _getBrightness(ThemeData theme) {
return colorBrightness ?? theme.brightness; return colorBrightness ?? theme.brightness;
} }
...@@ -350,6 +367,7 @@ class RaisedButton extends StatelessWidget { ...@@ -350,6 +367,7 @@ class RaisedButton extends StatelessWidget {
return new RawMaterialButton( return new RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
fillColor: fillColor, fillColor: fillColor,
textStyle: theme.textTheme.button.copyWith(color: textColor), textStyle: theme.textTheme.button.copyWith(color: textColor),
highlightColor: _getHighlightColor(theme, buttonTheme), highlightColor: _getHighlightColor(theme, buttonTheme),
...@@ -360,6 +378,7 @@ class RaisedButton extends StatelessWidget { ...@@ -360,6 +378,7 @@ class RaisedButton extends StatelessWidget {
padding: padding ?? buttonTheme.padding, padding: padding ?? buttonTheme.padding,
constraints: buttonTheme.constraints, constraints: buttonTheme.constraints,
shape: shape ?? buttonTheme.shape, shape: shape ?? buttonTheme.shape,
animationDuration: animationDuration,
child: child, child: child,
); );
} }
......
...@@ -78,7 +78,7 @@ class ScrollController extends ChangeNotifier { ...@@ -78,7 +78,7 @@ class ScrollController extends ChangeNotifier {
/// See also: /// See also:
/// ///
/// * [PageStorageKey], which should be used when more than one /// * [PageStorageKey], which should be used when more than one
//// scrollable appears in the same route, to distinguish the [PageStorage] /// scrollable appears in the same route, to distinguish the [PageStorage]
/// locations used to save scroll offsets. /// locations used to save scroll offsets.
final bool keepScrollOffset; final bool keepScrollOffset;
......
// Copyright 2018 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 'dart:ui' show SemanticsFlag;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Outline button responds to tap when enabled', (WidgetTester tester) async {
int pressedCount = 0;
Widget buildFrame(VoidCallback onPressed) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(),
child: new Center(
child: new OutlineButton(onPressed: onPressed),
),
),
);
}
await tester.pumpWidget(
buildFrame(() { pressedCount += 1; }),
);
expect(tester.widget<OutlineButton>(find.byType(OutlineButton)).enabled, true);
await tester.tap(find.byType(OutlineButton));
await tester.pumpAndSettle();
expect(pressedCount, 1);
await tester.pumpWidget(
buildFrame(null),
);
final Finder outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, false);
await tester.tap(outlineButton);
await tester.pumpAndSettle();
expect(pressedCount, 1);
});
testWidgets('Outline shape and border overrides', (WidgetTester tester) async {
const Color fillColor = const Color(0xFF00FF00);
const Color borderColor = const Color(0xFFFF0000);
const Color highlightedBorderColor = const Color(0xFF0000FF);
const double borderWidth = 4.0;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(),
child: new Container(
alignment: Alignment.topLeft,
child: new OutlineButton(
shape: const RoundedRectangleBorder(), // default border radius is 0
color: fillColor,
highlightedBorderColor: highlightedBorderColor,
borderSide: const BorderSide(
width: borderWidth,
color: borderColor,
),
onPressed: () { },
child: const Text('button')
),
),
),
),
);
final Finder outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, true);
final Rect clipRect = new Rect.fromLTRB(0.0, 0.0, 116.0, 36.0);
final Path clipPath = new Path()..addRect(clipRect);
expect(
outlineButton,
paints
// initially the interior of the button is transparent
..path(color: fillColor.withAlpha(0x00))
..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)))
..path(color: borderColor, strokeWidth: borderWidth)
);
final Offset center = tester.getCenter(outlineButton);
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
// Wait for the border's color to change to highlightedBorderColor and
// the fillColor to become opaque.
await tester.pump(const Duration(milliseconds: 200));
expect(
outlineButton,
paints
..path(color: fillColor.withAlpha(0xFF))
..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)))
..path(color: highlightedBorderColor, strokeWidth: borderWidth)
);
// Tap gesture completes, button returns to its initial configuration.
await gesture.up();
await tester.pumpAndSettle();
expect(
outlineButton,
paints
..path(color: fillColor.withAlpha(0x00))
..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)))
..path(color: borderColor, strokeWidth: borderWidth)
);
});
testWidgets('OutlineButton contributes semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new OutlineButton(
onPressed: () { },
child: const Text('ABC')
),
),
),
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
actions: <SemanticsAction>[
SemanticsAction.tap,
],
label: 'ABC',
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
flags: <SemanticsFlag>[
SemanticsFlag.isButton,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
)
],
),
ignoreId: true,
));
semantics.dispose();
});
testWidgets('OutlineButton scales textScaleFactor', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new MediaQuery(
data: const MediaQueryData(textScaleFactor: 1.0),
child: new Center(
child: new OutlineButton(
onPressed: () { },
child: const Text('ABC'),
),
),
),
),
),
);
expect(tester.getSize(find.byType(OutlineButton)), equals(const Size(88.0, 36.0)));
expect(tester.getSize(find.byType(Text)), equals(const Size(42.0, 14.0)));
// textScaleFactor expands text, but not button.
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new MediaQuery(
data: const MediaQueryData(textScaleFactor: 1.3),
child: new Center(
child: new FlatButton(
onPressed: () { },
child: const Text('ABC'),
),
),
),
),
),
);
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 36.0)));
// Scaled text rendering is different on Linux and Mac by one pixel.
// TODO(#12357): Update this test when text rendering is fixed.
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[54.0, 55.0]));
expect(tester.getSize(find.byType(Text)).height, isIn(<double>[18.0, 19.0]));
// Set text scale large enough to expand text and button.
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new MediaQuery(
data: const MediaQueryData(textScaleFactor: 3.0),
child: new Center(
child: new FlatButton(
onPressed: () { },
child: const Text('ABC'),
),
),
),
),
),
);
// Scaled text rendering is different on Linux and Mac by one pixel.
// TODO(#12357): Update this test when text rendering is fixed.
expect(tester.getSize(find.byType(FlatButton)).width, isIn(<double>[158.0, 159.0]));
expect(tester.getSize(find.byType(FlatButton)).height, equals(42.0));
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[126.0, 127.0]));
expect(tester.getSize(find.byType(Text)).height, equals(42.0));
});
testWidgets('OutlineButton implements debugFillDescription', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = new DiagnosticPropertiesBuilder();
new OutlineButton(
onPressed: () {},
textColor: const Color(0xFF00FF00),
disabledTextColor: const Color(0xFFFF0000),
color: const Color(0xFF000000),
highlightColor: const Color(0xFF1565C0),
splashColor: const Color(0xFF9E9E9E),
child: const Text('Hello'),
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
'textColor: Color(0xff00ff00)',
'disabledTextColor: Color(0xffff0000)',
'color: Color(0xff000000)',
'highlightColor: Color(0xff1565c0)',
'splashColor: Color(0xff9e9e9e)',
]);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment