mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
[Material] Add support for hovered, pressed, and focused text color on Buttons. (#33090)
* Support for stateful text colors in buttons * Add color and a11y tests for buttons
This commit is contained in:
parent
50ea6f32d3
commit
1b87f558d5
|
@ -76,6 +76,7 @@ export 'src/material/list_tile.dart';
|
|||
export 'src/material/material.dart';
|
||||
export 'src/material/material_button.dart';
|
||||
export 'src/material/material_localizations.dart';
|
||||
export 'src/material/material_state.dart';
|
||||
export 'src/material/mergeable_material.dart';
|
||||
export 'src/material/outline_button.dart';
|
||||
export 'src/material/page.dart';
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'button_theme.dart';
|
|||
import 'constants.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
import 'material_state.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
|
@ -85,6 +86,14 @@ class RawMaterialButton extends StatefulWidget {
|
|||
|
||||
/// Defines the default text style, with [Material.textStyle], for the
|
||||
/// button's [child].
|
||||
///
|
||||
/// If [textStyle.color] is a [MaterialStateColor], [MaterialStateColor.resolveColor]
|
||||
/// is used for the following [MaterialState]s:
|
||||
///
|
||||
/// * [MaterialState.pressed].
|
||||
/// * [MaterialState.hovered].
|
||||
/// * [MaterialState.focused].
|
||||
/// * [MaterialState.disabled].
|
||||
final TextStyle textStyle;
|
||||
|
||||
/// The color of the button's [Material].
|
||||
|
@ -231,14 +240,21 @@ class RawMaterialButton extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _RawMaterialButtonState extends State<RawMaterialButton> {
|
||||
bool _highlight = false;
|
||||
bool _focused = false;
|
||||
bool _hovering = false;
|
||||
final Set<MaterialState> _states = <MaterialState>{};
|
||||
|
||||
bool get _hovered => _states.contains(MaterialState.hovered);
|
||||
bool get _focused => _states.contains(MaterialState.focused);
|
||||
bool get _pressed => _states.contains(MaterialState.pressed);
|
||||
bool get _disabled => _states.contains(MaterialState.disabled);
|
||||
|
||||
void _updateState(MaterialState state, bool value) {
|
||||
value ? _states.add(state) : _states.remove(state);
|
||||
}
|
||||
|
||||
void _handleHighlightChanged(bool value) {
|
||||
if (_highlight != value) {
|
||||
if (_pressed != value) {
|
||||
setState(() {
|
||||
_highlight = value;
|
||||
_updateState(MaterialState.pressed, value);
|
||||
if (widget.onHighlightChanged != null) {
|
||||
widget.onHighlightChanged(value);
|
||||
}
|
||||
|
@ -246,46 +262,71 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(RawMaterialButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (_highlight && !widget.enabled) {
|
||||
_highlight = false;
|
||||
if (widget.onHighlightChanged != null) {
|
||||
widget.onHighlightChanged(false);
|
||||
}
|
||||
void _handleHoveredChanged(bool value) {
|
||||
if (_hovered != value) {
|
||||
setState(() {
|
||||
_updateState(MaterialState.hovered, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
double _effectiveElevation() {
|
||||
if (widget.enabled) {
|
||||
// These conditionals are in order of precedence, so be careful about
|
||||
// reorganizing them.
|
||||
if (_highlight) {
|
||||
return widget.highlightElevation;
|
||||
}
|
||||
if (_hovering) {
|
||||
return widget.hoverElevation;
|
||||
}
|
||||
if (_focused) {
|
||||
return widget.focusElevation;
|
||||
}
|
||||
return widget.elevation;
|
||||
} else {
|
||||
void _handleFocusedChanged(bool value) {
|
||||
if (_focused != value) {
|
||||
setState(() {
|
||||
_updateState(MaterialState.focused, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateState(MaterialState.disabled, !widget.enabled);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(RawMaterialButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_updateState(MaterialState.disabled, !widget.enabled);
|
||||
// If the button is disabled while a press gesture is currently ongoing,
|
||||
// InkWell makes a call to handleHighlightChanged. This causes an exception
|
||||
// because it calls setState in the middle of a build. To preempt this, we
|
||||
// manually update pressed to false when this situation occurs.
|
||||
if (_disabled && _pressed) {
|
||||
_handleHighlightChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
double get _effectiveElevation {
|
||||
// These conditionals are in order of precedence, so be careful about
|
||||
// reorganizing them.
|
||||
if (_disabled) {
|
||||
return widget.disabledElevation;
|
||||
}
|
||||
if (_pressed) {
|
||||
return widget.highlightElevation;
|
||||
}
|
||||
if (_hovered) {
|
||||
return widget.hoverElevation;
|
||||
}
|
||||
if (_focused) {
|
||||
return widget.focusElevation;
|
||||
}
|
||||
return widget.elevation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color effectiveTextColor = MaterialStateColor.resolveColor(widget.textStyle?.color, _states);
|
||||
|
||||
final Widget result = Focus(
|
||||
focusNode: widget.focusNode,
|
||||
onFocusChange: (bool focused) => setState(() { _focused = focused; }),
|
||||
onFocusChange: _handleFocusedChanged,
|
||||
child: ConstrainedBox(
|
||||
constraints: widget.constraints,
|
||||
child: Material(
|
||||
elevation: _effectiveElevation(),
|
||||
textStyle: widget.textStyle,
|
||||
elevation: _effectiveElevation,
|
||||
textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
|
||||
shape: widget.shape,
|
||||
color: widget.fillColor,
|
||||
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
|
||||
|
@ -297,11 +338,11 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
|
|||
highlightColor: widget.highlightColor,
|
||||
focusColor: widget.focusColor,
|
||||
hoverColor: widget.hoverColor,
|
||||
onHover: (bool hovering) => setState(() => _hovering = hovering),
|
||||
onHover: _handleHoveredChanged,
|
||||
onTap: widget.onPressed,
|
||||
customBorder: widget.shape,
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(color: widget.textStyle?.color),
|
||||
data: IconThemeData(color: effectiveTextColor),
|
||||
child: Container(
|
||||
padding: widget.padding,
|
||||
child: Center(
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'colors.dart';
|
|||
import 'constants.dart';
|
||||
import 'flat_button.dart';
|
||||
import 'material_button.dart';
|
||||
import 'material_state.dart';
|
||||
import 'outline_button.dart';
|
||||
import 'raised_button.dart';
|
||||
import 'theme.dart';
|
||||
|
@ -479,7 +480,13 @@ class ButtonThemeData extends Diagnosticable {
|
|||
/// Returns the button's [MaterialButton.disabledColor] if it is non-null.
|
||||
/// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
|
||||
/// with its opacity set to 0.30 if [getBrightness] is dark, 0.38 otherwise.
|
||||
///
|
||||
/// If [MaterialButton.textColor] is a [MaterialStateColor], it will be used
|
||||
/// as the `disabledTextColor`. It will be resolved in the
|
||||
/// [MaterialState.disabled] state.
|
||||
Color getDisabledTextColor(MaterialButton button) {
|
||||
if (button.textColor is MaterialStateColor)
|
||||
return button.textColor;
|
||||
if (button.disabledTextColor != null)
|
||||
return button.disabledTextColor;
|
||||
return _getDisabledColor(button);
|
||||
|
|
|
@ -100,6 +100,9 @@ class MaterialButton extends StatelessWidget {
|
|||
/// The default text color depends on the button theme's text theme,
|
||||
/// [ButtonThemeData.textTheme].
|
||||
///
|
||||
/// If [textColor] is a [MaterialStateColor], [disabledTextColor] will be
|
||||
/// ignored.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [disabledTextColor], the text color to use when the button has been
|
||||
|
@ -114,6 +117,9 @@ class MaterialButton extends StatelessWidget {
|
|||
/// The default value is the theme's disabled color,
|
||||
/// [ThemeData.disabledColor].
|
||||
///
|
||||
/// If [textColor] is a [MaterialStateColor], [disabledTextColor] will be
|
||||
/// ignored.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [textColor] - The color to use for this button's text when the button is [enabled].
|
||||
|
|
186
packages/flutter/lib/src/material/material_state.dart
Normal file
186
packages/flutter/lib/src/material/material_state.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2019 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 Color;
|
||||
|
||||
/// Interactive states that some of the Material widgets can take on when
|
||||
/// receiving input from the user.
|
||||
///
|
||||
/// States are defined by https://material.io/design/interaction/states.html#usage.
|
||||
///
|
||||
/// Some Material widgets track their current state in a `Set<MaterialState>`.
|
||||
///
|
||||
/// See also:
|
||||
/// * [MaterialStateColor], a color that has a `resolve` method that can
|
||||
/// return a different color depending on the state of the widget that it
|
||||
/// is used in.
|
||||
enum MaterialState {
|
||||
/// The state when the user drags their mouse cursor over the given widget.
|
||||
///
|
||||
/// See: https://material.io/design/interaction/states.html#hover.
|
||||
hovered,
|
||||
|
||||
/// The state when the user navigates with the keyboard to a given widget.
|
||||
///
|
||||
/// This can also sometimes be triggered when a widget is tapped. For example,
|
||||
/// when a [TextField] is tapped, it becomes [focused].
|
||||
///
|
||||
/// See: https://material.io/design/interaction/states.html#focus.
|
||||
focused,
|
||||
|
||||
/// The state when the user is actively pressing down on the given widget.
|
||||
///
|
||||
/// See: https://material.io/design/interaction/states.html#pressed.
|
||||
pressed,
|
||||
|
||||
/// The state when this widget is being dragged from one place to another by
|
||||
/// the user.
|
||||
///
|
||||
/// https://material.io/design/interaction/states.html#dragged.
|
||||
dragged,
|
||||
|
||||
/// The state when this item has been selected.
|
||||
///
|
||||
/// This applies to things that can be toggled (such as chips and checkboxes)
|
||||
/// and things that are selected from a set of options (such as tabs and radio buttons).
|
||||
///
|
||||
/// See: https://material.io/design/interaction/states.html#selected.
|
||||
selected,
|
||||
|
||||
/// The state when this widget disabled and can not be interacted with.
|
||||
///
|
||||
/// Disabled widgets should not respond to hover, focus, press, or drag
|
||||
/// interactions.
|
||||
///
|
||||
/// See: https://material.io/design/interaction/states.html#disabled.
|
||||
disabled,
|
||||
|
||||
/// The state when the widget has entered some form of invalid state.
|
||||
///
|
||||
/// See https://material.io/design/interaction/states.html#usage.
|
||||
error,
|
||||
}
|
||||
|
||||
/// Signature for the function that returns a color based on a given set of states.
|
||||
typedef MaterialStateColorResolver = Color Function(Set<MaterialState> states);
|
||||
|
||||
/// Defines a [Color] whose value depends on a set of [MaterialState]s which
|
||||
/// represent the interactive state of a component.
|
||||
///
|
||||
/// This is useful for improving the accessibility of text in different states
|
||||
/// of a component. For example, in a [FlatButton] with blue text, the text will
|
||||
/// become more difficult to read when the button is hovered, focused, or pressed,
|
||||
/// because the contrast ratio between the button and the text will decrease. To
|
||||
/// solve this, you can use [MaterialStateColor] to make the text darker when the
|
||||
/// [FlatButton] is hovered, focused, or pressed.
|
||||
///
|
||||
/// To use a [MaterialStateColor], you can either:
|
||||
/// 1. Create a subclass of [MaterialStateColor] and implement the abstract `resolve` method.
|
||||
/// 2. Use [MaterialStateColor.resolveWith] and pass in a callback that
|
||||
/// will be used to resolve the color in the given states.
|
||||
///
|
||||
/// This should only be used as parameters when they are documented to take
|
||||
/// [MaterialStateColor], otherwise only the default state will be used.
|
||||
///
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// This example shows how you could pass a `MaterialStateColor` to `FlatButton.textColor`.
|
||||
/// Here, the text color will be `Colors.blue[900]` when the button is being
|
||||
/// pressed, hovered, or focused. Otherwise, the text color will be `Colors.blue[600]`.
|
||||
///
|
||||
/// ```dart
|
||||
/// Color getTextColor(Set<MaterialState> states) {
|
||||
/// const Set<MaterialState> interactiveStates = <MaterialState>{
|
||||
/// MaterialState.pressed,
|
||||
/// MaterialState.hovered,
|
||||
/// MaterialState.focused,
|
||||
/// };
|
||||
/// if (states.any(interactiveStates.contains)) {
|
||||
/// return Colors.blue[900];
|
||||
/// }
|
||||
/// return Colors.blue[600];
|
||||
/// }
|
||||
///
|
||||
/// FlatButton(
|
||||
/// child: Text('FlatButton'),
|
||||
/// onPressed: () {},
|
||||
/// textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
/// ),
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
abstract class MaterialStateColor extends Color {
|
||||
/// Creates a [MaterialStateColor].
|
||||
///
|
||||
/// If you want a `const` [MaterialStateColor], you'll need to extend
|
||||
/// [MaterialStateColor] and override the [resolve] method. You'll also need
|
||||
/// to provide a `defaultValue` to the super constructor, so that we can know
|
||||
/// at compile-time what the value of the default [Color] is.
|
||||
///
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// In this next example, we see how you can create a `MaterialStateColor` by
|
||||
/// extending the abstract class and overriding the `resolve` method.
|
||||
///
|
||||
/// ```dart
|
||||
/// class TextColor extends MaterialStateColor {
|
||||
/// static const int _defaultColor = 0xcafefeed;
|
||||
/// static const int _pressedColor = 0xdeadbeef;
|
||||
///
|
||||
/// const TextColor() : super(_defaultColor);
|
||||
///
|
||||
/// @override
|
||||
/// Color resolve(Set<MaterialState> states) {
|
||||
/// if (states.contains(MaterialState.pressed)) {
|
||||
/// return const Color(_pressedColor);
|
||||
/// }
|
||||
/// return const Color(_defaultColor);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
const MaterialStateColor(int defaultValue) : super(defaultValue);
|
||||
|
||||
/// Creates a [MaterialStateColor] from a [MaterialStateColorResolver] callback function.
|
||||
///
|
||||
/// If used as a regular color, the color resolved in the default state (the
|
||||
/// empty set of states) will be used.
|
||||
///
|
||||
/// The given callback parameter must return a non-null color in the default
|
||||
/// state.
|
||||
factory MaterialStateColor.resolveWith(MaterialStateColorResolver callback) => _MaterialStateColor(callback);
|
||||
|
||||
/// Returns a [Color] that's to be used when a Material component is in the
|
||||
/// specified state.
|
||||
Color resolve(Set<MaterialState> states);
|
||||
|
||||
/// Returns the color for the given set of states if `color` is a
|
||||
/// [MaterialStateColor], otherwise returns the color itself.
|
||||
///
|
||||
/// This is useful for widgets that have parameters which can be [Color] or
|
||||
/// [MaterialStateColor] values.
|
||||
static Color resolveColor(Color color, Set<MaterialState> states) {
|
||||
if (color is MaterialStateColor) {
|
||||
return color.resolve(states);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/// A [MaterialStateColor] created from a [MaterialStateColorResolver] callback alone.
|
||||
///
|
||||
/// If used as a regular color, the color resolved in the default state will
|
||||
/// be used.
|
||||
///
|
||||
/// Used by [MaterialStateColor.resolveWith].
|
||||
class _MaterialStateColor extends MaterialStateColor {
|
||||
_MaterialStateColor(this._resolve) : super(_resolve(_defaultStates).value);
|
||||
|
||||
final MaterialStateColorResolver _resolve;
|
||||
|
||||
/// The default state for a Material component, the empty set of interaction states.
|
||||
static const Set<MaterialState> _defaultStates = <MaterialState>{};
|
||||
|
||||
@override
|
||||
Color resolve(Set<MaterialState> states) => _resolve(states);
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -325,4 +327,79 @@ void main() {
|
|||
expect(fooText, findsNWidgets(2));
|
||||
expect(tester.getRect(fooText.at(0)), tester.getRect(fooText.at(1)));
|
||||
});
|
||||
|
||||
testWidgets('button theme with stateful color changes color in states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: ButtonTheme(
|
||||
colorScheme: colorScheme.copyWith(
|
||||
primary: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
textTheme: ButtonTextTheme.primary,
|
||||
child: FlatButton(
|
||||
child: const Text('FlatButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('FlatButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Default, not disabled.
|
||||
expect(textColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(FlatButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(textColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
|
@ -33,6 +34,8 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Default FlatButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
|
@ -40,6 +43,7 @@ void main() {
|
|||
child: FlatButton(
|
||||
child: const Text('FlatButton'),
|
||||
onPressed: () { },
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -49,16 +53,266 @@ void main() {
|
|||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(FlatButton));
|
||||
await tester.startGesture(center);
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
|
||||
testWidgets('FlatButton with colored theme meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
final ColorScheme colorScheme = ColorScheme.fromSwatch(primarySwatch: Colors.blue);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
final Set<MaterialState> interactiveStates = <MaterialState>{
|
||||
MaterialState.pressed,
|
||||
MaterialState.hovered,
|
||||
MaterialState.focused,
|
||||
};
|
||||
if (states.any(interactiveStates.contains)) {
|
||||
return Colors.blue[900];
|
||||
}
|
||||
return Colors.blue[800];
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: ButtonTheme(
|
||||
colorScheme: colorScheme,
|
||||
textTheme: ButtonTextTheme.primary,
|
||||
child: FlatButton(
|
||||
child: const Text('FlatButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(FlatButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
|
||||
testWidgets('FlatButton uses stateful color for text color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: FlatButton(
|
||||
child: const Text('FlatButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('FlatButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Default, not disabled.
|
||||
expect(textColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(FlatButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(textColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('FlatButton uses stateful color for icon color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final Key buttonKey = UniqueKey();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: FlatButton.icon(
|
||||
key: buttonKey,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('FlatButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color iconColor() => _iconStyle(tester, Icons.add).color;
|
||||
// Default, not disabled.
|
||||
expect(iconColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byKey(buttonKey));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(iconColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('FlatButton ignores disabled text color if text color is stateful', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color disabledColor = Color(1);
|
||||
const Color defaultColor = Color(2);
|
||||
const Color unusedDisabledTextColor = Color(3);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: FlatButton(
|
||||
onPressed: null,
|
||||
child: const Text('FlatButton'),
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
disabledTextColor: unusedDisabledTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('FlatButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Disabled.
|
||||
expect(textColor(), equals(disabledColor));
|
||||
expect(textColor(), isNot(unusedDisabledTextColor));
|
||||
});
|
||||
|
||||
testWidgets('FlatButton has no clip by default', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
|
@ -78,3 +332,10 @@ void main() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||
final RichText iconRichText = tester.widget<RichText>(
|
||||
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
|
||||
);
|
||||
return iconRichText.text.style;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
@ -36,6 +37,8 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Default OutlineButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
|
@ -43,6 +46,7 @@ void main() {
|
|||
child: OutlineButton(
|
||||
child: const Text('OutlineButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -52,16 +56,266 @@ void main() {
|
|||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(OutlineButton));
|
||||
await tester.startGesture(center);
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
|
||||
testWidgets('OutlineButton with colored theme meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
final ColorScheme colorScheme = ColorScheme.fromSwatch(primarySwatch: Colors.blue);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
final Set<MaterialState> interactiveStates = <MaterialState>{
|
||||
MaterialState.pressed,
|
||||
MaterialState.hovered,
|
||||
MaterialState.focused,
|
||||
};
|
||||
if (states.any(interactiveStates.contains)) {
|
||||
return Colors.blue[900];
|
||||
}
|
||||
return Colors.blue[800];
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: ButtonTheme(
|
||||
colorScheme: colorScheme,
|
||||
textTheme: ButtonTextTheme.primary,
|
||||
child: OutlineButton(
|
||||
child: const Text('OutlineButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(OutlineButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
|
||||
testWidgets('OutlineButton uses stateful color for text color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: OutlineButton(
|
||||
child: const Text('OutlineButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('OutlineButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Default, not disabled.
|
||||
expect(textColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(OutlineButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(textColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('OutlineButton uses stateful color for icon color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final Key buttonKey = UniqueKey();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: OutlineButton.icon(
|
||||
key: buttonKey,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('OutlineButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color iconColor() => _iconStyle(tester, Icons.add).color;
|
||||
// Default, not disabled.
|
||||
expect(iconColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byKey(buttonKey));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(iconColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('OutlineButton ignores disabled text color if text color is stateful', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color disabledColor = Color(1);
|
||||
const Color defaultColor = Color(2);
|
||||
const Color unusedDisabledTextColor = Color(3);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: OutlineButton(
|
||||
onPressed: null,
|
||||
child: const Text('OutlineButton'),
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
disabledTextColor: unusedDisabledTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('OutlineButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Disabled.
|
||||
expect(textColor(), equals(disabledColor));
|
||||
expect(textColor(), isNot(unusedDisabledTextColor));
|
||||
});
|
||||
|
||||
testWidgets('Outline button responds to tap when enabled', (WidgetTester tester) async {
|
||||
int pressedCount = 0;
|
||||
|
||||
|
@ -448,3 +702,10 @@ void _checkPhysicalLayer(Element element, Color expectedColor, { Path clipPath,
|
|||
expect(expectedLayer.clipPath, coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)));
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||
final RichText iconRichText = tester.widget<RichText>(
|
||||
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
|
||||
);
|
||||
return iconRichText.text.style;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
@ -33,6 +34,8 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Default RaisedButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
|
@ -40,6 +43,7 @@ void main() {
|
|||
child: RaisedButton(
|
||||
child: const Text('RaisedButton'),
|
||||
onPressed: () { },
|
||||
focusNode: focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -49,15 +53,207 @@ void main() {
|
|||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(RaisedButton));
|
||||
await tester.startGesture(center);
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
await gesture.removePointer();
|
||||
},
|
||||
semanticsEnabled: true,
|
||||
);
|
||||
|
||||
testWidgets('RaisedButton uses stateful color for text color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: RaisedButton(
|
||||
child: const Text('RaisedButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('RaisedButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Default, not disabled.
|
||||
expect(textColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(RaisedButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(textColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(textColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
|
||||
testWidgets('RaisedButton uses stateful color for icon color in different states', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final Key buttonKey = UniqueKey();
|
||||
|
||||
const Color pressedColor = Color(1);
|
||||
const Color hoverColor = Color(2);
|
||||
const Color focusedColor = Color(3);
|
||||
const Color defaultColor = Color(4);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: RaisedButton.icon(
|
||||
key: buttonKey,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('RaisedButton'),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color iconColor() => _iconStyle(tester, Icons.add).color;
|
||||
// Default, not disabled.
|
||||
expect(iconColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byKey(buttonKey));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(iconColor(), pressedColor);
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('RaisedButton ignores disabled text color if text color is stateful', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color disabledColor = Color(1);
|
||||
const Color defaultColor = Color(2);
|
||||
const Color unusedDisabledTextColor = Color(3);
|
||||
|
||||
Color getTextColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: RaisedButton(
|
||||
onPressed: null,
|
||||
child: const Text('RaisedButton'),
|
||||
focusNode: focusNode,
|
||||
textColor: MaterialStateColor.resolveWith(getTextColor),
|
||||
disabledTextColor: unusedDisabledTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color textColor() {
|
||||
return tester.renderObject<RenderParagraph>(find.text('RaisedButton')).text.style.color;
|
||||
}
|
||||
|
||||
// Disabled.
|
||||
expect(textColor(), equals(disabledColor));
|
||||
expect(textColor(), isNot(unusedDisabledTextColor));
|
||||
});
|
||||
}
|
||||
|
||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||
final RichText iconRichText = tester.widget<RichText>(
|
||||
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
|
||||
);
|
||||
return iconRichText.text.style;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue