Reland: Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#142762)
Reland https://github.com/flutter/flutter/pull/141818 with a fix for a special case: If only `background` is specified for `TextButton.styleFrom` or `OutlinedButton.styleFrom` it applies the button's disabled state, i.e. as if the same value had been specified for disabledBackgroundColor. The change relative to #141818 is the indicated line below: ```dart final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { (null, null) => null, (_, null) => MaterialStatePropertyAll<Color?>(backgroundColor), // ADDED THIS LINE (_, _) => _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor), }; ``` This backwards incompatibility cropped up in an internal test, see internal Google issue b/323399158.
This commit is contained in:
parent
c13ebf1e78
commit
c6f2cea65e
@ -374,7 +374,6 @@ final Set<String> _knownMissingTests = <String>{
|
|||||||
'examples/api/test/material/checkbox/checkbox.1_test.dart',
|
'examples/api/test/material/checkbox/checkbox.1_test.dart',
|
||||||
'examples/api/test/material/checkbox/checkbox.0_test.dart',
|
'examples/api/test/material/checkbox/checkbox.0_test.dart',
|
||||||
'examples/api/test/material/navigation_rail/navigation_rail.extended_animation.0_test.dart',
|
'examples/api/test/material/navigation_rail/navigation_rail.extended_animation.0_test.dart',
|
||||||
'examples/api/test/material/text_button/text_button.0_test.dart',
|
|
||||||
'examples/api/test/rendering/growth_direction/growth_direction.0_test.dart',
|
'examples/api/test/rendering/growth_direction/growth_direction.0_test.dart',
|
||||||
'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0_test.dart',
|
'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0_test.dart',
|
||||||
'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1_test.dart',
|
'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1_test.dart',
|
||||||
|
@ -6,78 +6,461 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
/// Flutter code sample for [TextButton].
|
/// Flutter code sample for [TextButton].
|
||||||
|
|
||||||
void main() => runApp(const TextButtonExampleApp());
|
void main() {
|
||||||
|
runApp(const TextButtonExampleApp());
|
||||||
|
}
|
||||||
|
|
||||||
class TextButtonExampleApp extends StatelessWidget {
|
class TextButtonExampleApp extends StatefulWidget {
|
||||||
const TextButtonExampleApp({super.key});
|
const TextButtonExampleApp({ super.key });
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TextButtonExampleApp> createState() => _TextButtonExampleAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextButtonExampleAppState extends State<TextButtonExampleApp> {
|
||||||
|
bool darkMode = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
themeMode: darkMode ? ThemeMode.dark : ThemeMode.light,
|
||||||
|
theme: ThemeData(brightness: Brightness.light),
|
||||||
|
darkTheme: ThemeData(brightness: Brightness.dark),
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
appBar: AppBar(title: const Text('TextButton Sample')),
|
body: Padding(
|
||||||
body: const TextButtonExample(),
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: TextButtonExample(
|
||||||
|
darkMode: darkMode,
|
||||||
|
updateDarkMode: (bool value) {
|
||||||
|
setState(() { darkMode = value; });
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextButtonExample extends StatelessWidget {
|
class TextButtonExample extends StatefulWidget {
|
||||||
const TextButtonExample({super.key});
|
const TextButtonExample({ super.key, required this.darkMode, required this.updateDarkMode });
|
||||||
|
|
||||||
|
final bool darkMode;
|
||||||
|
final ValueChanged<bool> updateDarkMode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TextButtonExample> createState() => _TextButtonExampleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextButtonExampleState extends State<TextButtonExample> {
|
||||||
|
TextDirection textDirection = TextDirection.ltr;
|
||||||
|
ThemeMode themeMode = ThemeMode.light;
|
||||||
|
late final ScrollController scrollController;
|
||||||
|
|
||||||
|
static const Widget verticalSpacer = SizedBox(height: 16);
|
||||||
|
static const Widget horizontalSpacer = SizedBox(width: 32);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
scrollController = ScrollController();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
final ThemeData theme = Theme.of(context);
|
||||||
child: Column(
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
// Adapt colors that are not part of the color scheme to
|
||||||
TextButton(
|
// the current dark/light mode. Used to define TextButton #7's
|
||||||
style: TextButton.styleFrom(
|
// gradients.
|
||||||
textStyle: const TextStyle(fontSize: 20),
|
final (Color color1, Color color2, Color color3) = switch (colorScheme.brightness) {
|
||||||
|
Brightness.light => (Colors.blue.withOpacity(1.0), Colors.orange.withOpacity(1.0), Colors.yellow.withOpacity(1.0)),
|
||||||
|
Brightness.dark => (Colors.purple.withOpacity(1.0), Colors.cyan.withOpacity(1.0), Colors.yellow.withOpacity(1.0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This gradient's appearance reflects the button's state.
|
||||||
|
// Always return a gradient decoration so that AnimatedContainer
|
||||||
|
// can interpolorate in between. Used by TextButton #7.
|
||||||
|
Decoration? statesToDecoration(Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.pressed)) {
|
||||||
|
return BoxDecoration(
|
||||||
|
gradient: LinearGradient(colors: <Color>[color2, color2]), // solid fill
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: switch (states.contains(MaterialState.hovered)) {
|
||||||
|
true => <Color>[color1, color2],
|
||||||
|
false => <Color>[color2, color1],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To make this method a little easier to read, the buttons that
|
||||||
|
// appear in the two columns to the right of the demo switches
|
||||||
|
// Card are broken out below.
|
||||||
|
|
||||||
|
final List<Widget> columnOneButtons = <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text('Enabled'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
const TextButton(
|
||||||
|
onPressed: null,
|
||||||
|
child: Text('Disabled'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.access_alarm),
|
||||||
|
label: const Text('TextButton.icon #1'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the foreground and background colors.
|
||||||
|
//
|
||||||
|
// In this example, and most of the ones that follow, we're using
|
||||||
|
// the TextButton.styleFrom() convenience method to create a ButtonStyle.
|
||||||
|
// The styleFrom method is a little easier because it creates
|
||||||
|
// ButtonStyle MaterialStateProperty parameters for you.
|
||||||
|
// In this case, Specifying foregroundColor overrides the text,
|
||||||
|
// icon and overlay (splash and highlight) colors a little differently
|
||||||
|
// depending on the button's state. BackgroundColor is just the background
|
||||||
|
// color for all states.
|
||||||
|
TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: colorScheme.onError,
|
||||||
|
backgroundColor: colorScheme.error,
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.access_alarm),
|
||||||
|
label: const Text('TextButton.icon #2'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the button's shape and its border.
|
||||||
|
//
|
||||||
|
// In this case we've specified a shape that has border - the
|
||||||
|
// RoundedRectangleBorder's side parameter. If the styleFrom
|
||||||
|
// side parameter was also specified, or if the TextButtonTheme
|
||||||
|
// defined above included a side parameter, then that would
|
||||||
|
// override the RoundedRectangleBorder's side.
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
side: BorderSide(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
width: 5,
|
||||||
),
|
),
|
||||||
onPressed: null,
|
|
||||||
child: const Text('Disabled'),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
),
|
||||||
TextButton(
|
onPressed: () { },
|
||||||
style: TextButton.styleFrom(
|
child: const Text('TextButton #3'),
|
||||||
textStyle: const TextStyle(fontSize: 20),
|
),
|
||||||
),
|
verticalSpacer,
|
||||||
onPressed: () {},
|
|
||||||
child: const Text('Enabled'),
|
// Override overlay: the ink splash and highlight colors.
|
||||||
),
|
//
|
||||||
const SizedBox(height: 30),
|
// The styleFrom method turns the specified overlayColor
|
||||||
ClipRRect(
|
// into a value MaterialStyleProperty<Color> ButtonStyle.overlay
|
||||||
borderRadius: BorderRadius.circular(4),
|
// value that uses opacities depending on the button's state.
|
||||||
child: Stack(
|
// If the overlayColor was Colors.transparent, no splash
|
||||||
children: <Widget>[
|
// or highlights would be shown.
|
||||||
Positioned.fill(
|
TextButton(
|
||||||
child: Container(
|
style: TextButton.styleFrom(
|
||||||
decoration: const BoxDecoration(
|
overlayColor: Colors.yellow,
|
||||||
gradient: LinearGradient(
|
),
|
||||||
colors: <Color>[
|
onPressed: () { },
|
||||||
Color(0xFF0D47A1),
|
child: const Text('TextButton #4'),
|
||||||
Color(0xFF1976D2),
|
),
|
||||||
Color(0xFF42A5F5),
|
];
|
||||||
],
|
|
||||||
),
|
final List<Widget> columnTwoButtons = <Widget>[
|
||||||
|
// Override the foregroundBuilder: apply a ShaderMask.
|
||||||
|
//
|
||||||
|
// Apply a ShaderMask to the button's child. This kind of thing
|
||||||
|
// can be applied to one button easily enough by just wrapping the
|
||||||
|
// button's child directly. However to affect all buttons in this
|
||||||
|
// way you can specify a similar foregroundBuilder in a TextButton
|
||||||
|
// theme or the MaterialApp theme's ThemeData.textButtonTheme.
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return ShaderMask(
|
||||||
|
shaderCallback: (Rect bounds) {
|
||||||
|
return LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: <Color>[
|
||||||
|
colorScheme.primary,
|
||||||
|
colorScheme.onPrimary,
|
||||||
|
],
|
||||||
|
).createShader(bounds);
|
||||||
|
},
|
||||||
|
blendMode: BlendMode.srcATop,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('TextButton #5'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the foregroundBuilder: add an underline.
|
||||||
|
//
|
||||||
|
// Add a border around button's child. In this case the
|
||||||
|
// border only appears when the button is hovered or pressed
|
||||||
|
// (if it's pressed it's always hovered too). Not that this
|
||||||
|
// border is different than the one specified with the styleFrom
|
||||||
|
// side parameter (or the ButtonStyle.side property). The foregroundBuilder
|
||||||
|
// is applied to a widget that contains the child and has already
|
||||||
|
// included the button's padding. It is unaffected by the button's shape.
|
||||||
|
// The styleFrom side parameter controls the button's outermost border and it
|
||||||
|
// outlines the button's shape.
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: states.contains(MaterialState.hovered)
|
||||||
|
? Border(bottom: BorderSide(color: colorScheme.primary))
|
||||||
|
: const Border(), // essentially "no border"
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('TextButton #6'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the backgroundBuilder to add a state specific gradient background
|
||||||
|
// and add an outline that only appears when the button is hovered or pressed.
|
||||||
|
//
|
||||||
|
// The gradient background decoration is computed by the statesToDecoration()
|
||||||
|
// method. The gradient flips horizontally when the button is hovered (watch
|
||||||
|
// closely). Because we want the outline to only appear when the button is hovered
|
||||||
|
// we can't use the styleFrom() side parameter, because that creates the same
|
||||||
|
// outline for all states. The ButtonStyle.copyWith() method is used to add
|
||||||
|
// a MaterialState<BorderSide?> property that does the right thing.
|
||||||
|
//
|
||||||
|
// The gradient background is translucent - all of the colors have opacity 0.5 -
|
||||||
|
// so the overlay's splash and highlight colors are visible even though they're
|
||||||
|
// drawn on the Material widget that's effectively behind the background. The
|
||||||
|
// border is also translucent, so if you look carefully, you'll see that the
|
||||||
|
// background - which is part of the button's Material but is drawn on top of the
|
||||||
|
// the background gradient - shows through the border.
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
overlayColor: color2,
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
decoration: statesToDecoration(states),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).copyWith(
|
||||||
|
side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.hovered)) {
|
||||||
|
return BorderSide(width: 3, color: color3);
|
||||||
|
}
|
||||||
|
return null; // defer to the default
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child: const Text('TextButton #7'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the backgroundBuilder to add a grass image background.
|
||||||
|
//
|
||||||
|
// The image is clipped to the button's shape. We've included an Ink widget
|
||||||
|
// because the background image is opaque and would otherwise obscure the splash
|
||||||
|
// and highlight overlays that are painted on the button's Material widget
|
||||||
|
// by default. They're drawn on the Ink widget instead. The foreground color
|
||||||
|
// was overridden as well because white shows up a little better on the mottled
|
||||||
|
// green background.
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return Ink(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: NetworkImage(grassUrl),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: const Text('TextButton #8'),
|
||||||
|
),
|
||||||
|
verticalSpacer,
|
||||||
|
|
||||||
|
// Override the foregroundBuilder to specify images for the button's pressed
|
||||||
|
// hovered and inactive states.
|
||||||
|
//
|
||||||
|
// This is an example of completely changing the default appearance of a button
|
||||||
|
// by specifying images for each state and by turning off the overlays by
|
||||||
|
// overlayColor: Colors.transparent. AnimatedContainer takes care of the
|
||||||
|
// fade in and out segues between images.
|
||||||
|
//
|
||||||
|
// This foregroundBuilder function ignores its child parameter. Unfortunately
|
||||||
|
// TextButton's child parameter is required, so we still have
|
||||||
|
// to provide one.
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
overlayColor: Colors.transparent,
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
String url = states.contains(MaterialState.hovered) ? smiley3Url : smiley1Url;
|
||||||
|
if (states.contains(MaterialState.pressed)) {
|
||||||
|
url = smiley2Url;
|
||||||
|
}
|
||||||
|
return AnimatedContainer(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: NetworkImage(url),
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: const Text('This child is not used'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: <Widget> [
|
||||||
|
// The dark/light and LTR/RTL switches. We use the updateDarkMode function
|
||||||
|
// provided by the parent TextButtonExampleApp to rebuild the MaterialApp
|
||||||
|
// in the appropriate dark/light ThemeMdoe. The directionality of the rest
|
||||||
|
// of the UI is controlled by the Directionality widget below, and the
|
||||||
|
// textDirection local state variable.
|
||||||
|
TextButtonExampleSwitches(
|
||||||
|
darkMode: widget.darkMode,
|
||||||
|
updateDarkMode: widget.updateDarkMode,
|
||||||
|
textDirection: textDirection,
|
||||||
|
updateRTL: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
textDirection = value ? TextDirection.rtl : TextDirection.ltr;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
horizontalSpacer,
|
||||||
|
|
||||||
|
// All of the button examples appear below. They're arranged in two columns.
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: scrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
controller: scrollController,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Directionality(
|
||||||
|
textDirection: textDirection,
|
||||||
|
child: Column(
|
||||||
|
children: columnOneButtons,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
horizontalSpacer,
|
||||||
TextButton(
|
|
||||||
style: TextButton.styleFrom(
|
Directionality(
|
||||||
foregroundColor: Colors.white,
|
textDirection: textDirection,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Column(
|
||||||
textStyle: const TextStyle(fontSize: 20),
|
children: columnTwoButtons
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {},
|
horizontalSpacer,
|
||||||
child: const Text('Gradient'),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextButtonExampleSwitches extends StatelessWidget {
|
||||||
|
const TextButtonExampleSwitches({
|
||||||
|
super.key,
|
||||||
|
required this.darkMode,
|
||||||
|
required this.updateDarkMode,
|
||||||
|
required this.textDirection,
|
||||||
|
required this.updateRTL
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool darkMode;
|
||||||
|
final ValueChanged<bool> updateDarkMode;
|
||||||
|
final TextDirection textDirection;
|
||||||
|
final ValueChanged<bool> updateRTL;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: IntrinsicWidth(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Expanded(child: Text('Dark Mode')),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Switch(
|
||||||
|
value: darkMode,
|
||||||
|
onChanged: updateDarkMode,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Expanded(child: Text('RTL Text')),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Switch(
|
||||||
|
value: textDirection == TextDirection.rtl,
|
||||||
|
onChanged: updateRTL,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const String grassUrl = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_grass.jpeg';
|
||||||
|
const String smiley1Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley1.png';
|
||||||
|
const String smiley2Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley2.png';
|
||||||
|
const String smiley3Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley3.png';
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2014 The Flutter 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:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_api_samples/material/text_button/text_button.0.dart' as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
// The app being tested loads images via HTTP which the test
|
||||||
|
// framework defeats by default.
|
||||||
|
setUpAll(() {
|
||||||
|
HttpOverrides.global = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButtonExample smoke test', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const example.TextButtonExampleApp());
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'Enabled'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'Disabled'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// TextButton.icon buttons are _TextButtonWithIcons rather than TextButtons.
|
||||||
|
// For the purposes of this test, just tapping in the right place is OK.
|
||||||
|
|
||||||
|
await tester.tap(find.text('TextButton.icon #1'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.text('TextButton.icon #2'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #3'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #4'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #5'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #6'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #7'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(TextButton, 'TextButton #8'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(TextButton).last); // Smiley image button
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(Switch).at(0)); // Dark Mode Switch
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(Switch).at(1)); // RTL Text Switch
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
}
|
@ -16,6 +16,12 @@ import 'theme_data.dart';
|
|||||||
// late BuildContext context;
|
// late BuildContext context;
|
||||||
// typedef MyAppHome = Placeholder;
|
// typedef MyAppHome = Placeholder;
|
||||||
|
|
||||||
|
/// The type for [ButtonStyle.backgroundBuilder] and [ButtonStyle.foregroundBuilder].
|
||||||
|
///
|
||||||
|
/// The [states] parameter is the button's current pressed/hovered/etc state. The [child] is
|
||||||
|
/// typically a descendant of the returned widget.
|
||||||
|
typedef ButtonLayerBuilder = Widget Function(BuildContext context, Set<MaterialState> states, Widget? child);
|
||||||
|
|
||||||
/// The visual properties that most buttons have in common.
|
/// The visual properties that most buttons have in common.
|
||||||
///
|
///
|
||||||
/// Buttons and their themes have a ButtonStyle property which defines the visual
|
/// Buttons and their themes have a ButtonStyle property which defines the visual
|
||||||
@ -162,6 +168,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
this.enableFeedback,
|
this.enableFeedback,
|
||||||
this.alignment,
|
this.alignment,
|
||||||
this.splashFactory,
|
this.splashFactory,
|
||||||
|
this.backgroundBuilder,
|
||||||
|
this.foregroundBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The style for a button's [Text] widget descendants.
|
/// The style for a button's [Text] widget descendants.
|
||||||
@ -315,6 +323,42 @@ class ButtonStyle with Diagnosticable {
|
|||||||
/// ```
|
/// ```
|
||||||
final InteractiveInkFeatureFactory? splashFactory;
|
final InteractiveInkFeatureFactory? splashFactory;
|
||||||
|
|
||||||
|
/// Creates a widget that becomes the child of the button's [Material]
|
||||||
|
/// and whose child is the rest of the button, including the button's
|
||||||
|
/// `child` parameter.
|
||||||
|
///
|
||||||
|
/// The widget created by [backgroundBuilder] is constrained to be
|
||||||
|
/// the same size as the overall button and will appear behind the
|
||||||
|
/// button's child. The widget created by [foregroundBuilder] is
|
||||||
|
/// constrained to be the same size as the button's child, i.e. it's
|
||||||
|
/// inset by [ButtonStyle.padding] and aligned by the button's
|
||||||
|
/// [ButtonStyle.alignment].
|
||||||
|
///
|
||||||
|
/// By default the returned widget is clipped to the Material's [ButtonStyle.shape].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [foregroundBuilder], to create a widget that's as big as the button's
|
||||||
|
/// child and is layered behind the child.
|
||||||
|
/// * [ButtonStyleButton.clipBehavior], for more information about
|
||||||
|
/// configuring clipping.
|
||||||
|
final ButtonLayerBuilder? backgroundBuilder;
|
||||||
|
|
||||||
|
/// Creates a Widget that contains the button's child parameter which is used
|
||||||
|
/// instead of the button's child.
|
||||||
|
///
|
||||||
|
/// The returned widget is clipped by the button's
|
||||||
|
/// [ButtonStyle.shape], inset by the button's [ButtonStyle.padding]
|
||||||
|
/// and aligned by the button's [ButtonStyle.alignment].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [backgroundBuilder], to create a widget that's as big as the button and
|
||||||
|
/// is layered behind the button's child.
|
||||||
|
/// * [ButtonStyleButton.clipBehavior], for more information about
|
||||||
|
/// configuring clipping.
|
||||||
|
final ButtonLayerBuilder? foregroundBuilder;
|
||||||
|
|
||||||
/// Returns a copy of this ButtonStyle with the given fields replaced with
|
/// Returns a copy of this ButtonStyle with the given fields replaced with
|
||||||
/// the new values.
|
/// the new values.
|
||||||
ButtonStyle copyWith({
|
ButtonStyle copyWith({
|
||||||
@ -340,6 +384,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
bool? enableFeedback,
|
bool? enableFeedback,
|
||||||
AlignmentGeometry? alignment,
|
AlignmentGeometry? alignment,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
|
ButtonLayerBuilder? backgroundBuilder,
|
||||||
|
ButtonLayerBuilder? foregroundBuilder,
|
||||||
}) {
|
}) {
|
||||||
return ButtonStyle(
|
return ButtonStyle(
|
||||||
textStyle: textStyle ?? this.textStyle,
|
textStyle: textStyle ?? this.textStyle,
|
||||||
@ -364,6 +410,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
enableFeedback: enableFeedback ?? this.enableFeedback,
|
enableFeedback: enableFeedback ?? this.enableFeedback,
|
||||||
alignment: alignment ?? this.alignment,
|
alignment: alignment ?? this.alignment,
|
||||||
splashFactory: splashFactory ?? this.splashFactory,
|
splashFactory: splashFactory ?? this.splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder ?? this.backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder ?? this.foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,6 +447,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
enableFeedback: enableFeedback ?? style.enableFeedback,
|
enableFeedback: enableFeedback ?? style.enableFeedback,
|
||||||
alignment: alignment ?? style.alignment,
|
alignment: alignment ?? style.alignment,
|
||||||
splashFactory: splashFactory ?? style.splashFactory,
|
splashFactory: splashFactory ?? style.splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder ?? style.backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder ?? style.foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,6 +477,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
enableFeedback,
|
enableFeedback,
|
||||||
alignment,
|
alignment,
|
||||||
splashFactory,
|
splashFactory,
|
||||||
|
backgroundBuilder,
|
||||||
|
foregroundBuilder,
|
||||||
];
|
];
|
||||||
return Object.hashAll(values);
|
return Object.hashAll(values);
|
||||||
}
|
}
|
||||||
@ -461,7 +513,9 @@ class ButtonStyle with Diagnosticable {
|
|||||||
&& other.animationDuration == animationDuration
|
&& other.animationDuration == animationDuration
|
||||||
&& other.enableFeedback == enableFeedback
|
&& other.enableFeedback == enableFeedback
|
||||||
&& other.alignment == alignment
|
&& other.alignment == alignment
|
||||||
&& other.splashFactory == splashFactory;
|
&& other.splashFactory == splashFactory
|
||||||
|
&& other.backgroundBuilder == backgroundBuilder
|
||||||
|
&& other.foregroundBuilder == foregroundBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -488,6 +542,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
properties.add(DiagnosticsProperty<Duration>('animationDuration', animationDuration, defaultValue: null));
|
properties.add(DiagnosticsProperty<Duration>('animationDuration', animationDuration, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
|
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
|
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<ButtonLayerBuilder>('backgroundBuilder', backgroundBuilder, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<ButtonLayerBuilder>('foregroundBuilder', foregroundBuilder, defaultValue: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Linearly interpolate between two [ButtonStyle]s.
|
/// Linearly interpolate between two [ButtonStyle]s.
|
||||||
@ -518,6 +574,8 @@ class ButtonStyle with Diagnosticable {
|
|||||||
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
|
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
|
||||||
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
|
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
|
||||||
splashFactory: t < 0.5 ? a?.splashFactory : b?.splashFactory,
|
splashFactory: t < 0.5 ? a?.splashFactory : b?.splashFactory,
|
||||||
|
backgroundBuilder: t < 0.5 ? a?.backgroundBuilder : b?.backgroundBuilder,
|
||||||
|
foregroundBuilder: t < 0.5 ? a?.foregroundBuilder : b?.foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,8 +89,10 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
|||||||
|
|
||||||
/// {@macro flutter.material.Material.clipBehavior}
|
/// {@macro flutter.material.Material.clipBehavior}
|
||||||
///
|
///
|
||||||
/// Defaults to [Clip.none].
|
/// Defaults to [Clip.none] unless [ButtonStyle.backgroundBuilder] or
|
||||||
final Clip clipBehavior;
|
/// [ButtonStyle.foregroundBuilder] is specified. In those
|
||||||
|
/// cases the default is [Clip.antiAlias].
|
||||||
|
final Clip? clipBehavior;
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Focus.focusNode}
|
/// {@macro flutter.widgets.Focus.focusNode}
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
@ -318,6 +320,11 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
|
|||||||
final AlignmentGeometry? resolvedAlignment = effectiveValue((ButtonStyle? style) => style?.alignment);
|
final AlignmentGeometry? resolvedAlignment = effectiveValue((ButtonStyle? style) => style?.alignment);
|
||||||
final Offset densityAdjustment = resolvedVisualDensity!.baseSizeAdjustment;
|
final Offset densityAdjustment = resolvedVisualDensity!.baseSizeAdjustment;
|
||||||
final InteractiveInkFeatureFactory? resolvedSplashFactory = effectiveValue((ButtonStyle? style) => style?.splashFactory);
|
final InteractiveInkFeatureFactory? resolvedSplashFactory = effectiveValue((ButtonStyle? style) => style?.splashFactory);
|
||||||
|
final ButtonLayerBuilder? resolvedBackgroundBuilder = effectiveValue((ButtonStyle? style) => style?.backgroundBuilder);
|
||||||
|
final ButtonLayerBuilder? resolvedForegroundBuilder = effectiveValue((ButtonStyle? style) => style?.foregroundBuilder);
|
||||||
|
|
||||||
|
final Clip effectiveClipBehavior = widget.clipBehavior
|
||||||
|
?? ((resolvedBackgroundBuilder ?? resolvedForegroundBuilder) != null ? Clip.antiAlias : Clip.none);
|
||||||
|
|
||||||
BoxConstraints effectiveConstraints = resolvedVisualDensity.effectiveConstraints(
|
BoxConstraints effectiveConstraints = resolvedVisualDensity.effectiveConstraints(
|
||||||
BoxConstraints(
|
BoxConstraints(
|
||||||
@ -384,6 +391,21 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
|
|||||||
elevation = resolvedElevation;
|
elevation = resolvedElevation;
|
||||||
backgroundColor = resolvedBackgroundColor;
|
backgroundColor = resolvedBackgroundColor;
|
||||||
|
|
||||||
|
Widget effectiveChild = Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Align(
|
||||||
|
alignment: resolvedAlignment!,
|
||||||
|
widthFactor: 1.0,
|
||||||
|
heightFactor: 1.0,
|
||||||
|
child: resolvedForegroundBuilder != null
|
||||||
|
? resolvedForegroundBuilder(context, statesController.value, widget.child)
|
||||||
|
: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (resolvedBackgroundBuilder != null) {
|
||||||
|
effectiveChild = resolvedBackgroundBuilder(context, statesController.value, effectiveChild);
|
||||||
|
}
|
||||||
|
|
||||||
final Widget result = ConstrainedBox(
|
final Widget result = ConstrainedBox(
|
||||||
constraints: effectiveConstraints,
|
constraints: effectiveConstraints,
|
||||||
child: Material(
|
child: Material(
|
||||||
@ -395,7 +417,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
|
|||||||
surfaceTintColor: resolvedSurfaceTintColor,
|
surfaceTintColor: resolvedSurfaceTintColor,
|
||||||
type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button,
|
type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button,
|
||||||
animationDuration: resolvedAnimationDuration,
|
animationDuration: resolvedAnimationDuration,
|
||||||
clipBehavior: widget.clipBehavior,
|
clipBehavior: effectiveClipBehavior,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: widget.onPressed,
|
onTap: widget.onPressed,
|
||||||
onLongPress: widget.onLongPress,
|
onLongPress: widget.onLongPress,
|
||||||
@ -413,15 +435,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
|
|||||||
statesController: statesController,
|
statesController: statesController,
|
||||||
child: IconTheme.merge(
|
child: IconTheme.merge(
|
||||||
data: IconThemeData(color: resolvedIconColor ?? resolvedForegroundColor, size: resolvedIconSize),
|
data: IconThemeData(color: resolvedIconColor ?? resolvedForegroundColor, size: resolvedIconSize),
|
||||||
child: Padding(
|
child: effectiveChild,
|
||||||
padding: padding,
|
|
||||||
child: Align(
|
|
||||||
alignment: resolvedAlignment!,
|
|
||||||
widthFactor: 1.0,
|
|
||||||
heightFactor: 1.0,
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'button_style.dart';
|
import 'button_style.dart';
|
||||||
import 'button_style_button.dart';
|
import 'button_style_button.dart';
|
||||||
import 'color_scheme.dart';
|
import 'color_scheme.dart';
|
||||||
|
import 'colors.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
import 'elevated_button_theme.dart';
|
import 'elevated_button_theme.dart';
|
||||||
import 'ink_ripple.dart';
|
import 'ink_ripple.dart';
|
||||||
@ -70,7 +71,7 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
super.autofocus = false,
|
super.autofocus = false,
|
||||||
super.clipBehavior = Clip.none,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required super.child,
|
required super.child,
|
||||||
});
|
});
|
||||||
@ -132,19 +133,26 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
///
|
///
|
||||||
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
||||||
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
||||||
/// a derived [ButtonStyle.overlayColor].
|
/// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified.
|
||||||
|
///
|
||||||
|
/// If [overlayColor] is specified and its value is [Colors.transparent]
|
||||||
|
/// then the pressed/focused/hovered highlights are effectively defeated.
|
||||||
|
/// Otherwise a [MaterialStateProperty] with the same opacities as the
|
||||||
|
/// default is created.
|
||||||
///
|
///
|
||||||
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
||||||
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
||||||
///
|
///
|
||||||
|
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||||||
|
/// parameters are used to construct [ButtonStyle.mouseCursor] and
|
||||||
|
/// [iconColor], [disabledIconColor] are used to construct
|
||||||
|
/// [ButtonStyle.iconColor].
|
||||||
|
///
|
||||||
/// The button's elevations are defined relative to the [elevation]
|
/// The button's elevations are defined relative to the [elevation]
|
||||||
/// parameter. The disabled elevation is the same as the parameter
|
/// parameter. The disabled elevation is the same as the parameter
|
||||||
/// value, [elevation] + 2 is used when the button is hovered
|
/// value, [elevation] + 2 is used when the button is hovered
|
||||||
/// or focused, and elevation + 6 is used when the button is pressed.
|
/// or focused, and elevation + 6 is used when the button is pressed.
|
||||||
///
|
///
|
||||||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
|
||||||
/// parameters are used to construct [ButtonStyle].mouseCursor.
|
|
||||||
///
|
|
||||||
/// All of the other parameters are either used directly or used to
|
/// All of the other parameters are either used directly or used to
|
||||||
/// create a [MaterialStateProperty] with a single value for all
|
/// create a [MaterialStateProperty] with a single value for all
|
||||||
/// states.
|
/// states.
|
||||||
@ -186,6 +194,9 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
Color? disabledBackgroundColor,
|
Color? disabledBackgroundColor,
|
||||||
Color? shadowColor,
|
Color? shadowColor,
|
||||||
Color? surfaceTintColor,
|
Color? surfaceTintColor,
|
||||||
|
Color? iconColor,
|
||||||
|
Color? disabledIconColor,
|
||||||
|
Color? overlayColor,
|
||||||
double? elevation,
|
double? elevation,
|
||||||
TextStyle? textStyle,
|
TextStyle? textStyle,
|
||||||
EdgeInsetsGeometry? padding,
|
EdgeInsetsGeometry? padding,
|
||||||
@ -202,32 +213,40 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
bool? enableFeedback,
|
bool? enableFeedback,
|
||||||
AlignmentGeometry? alignment,
|
AlignmentGeometry? alignment,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
|
ButtonLayerBuilder? backgroundBuilder,
|
||||||
|
ButtonLayerBuilder? foregroundBuilder,
|
||||||
}) {
|
}) {
|
||||||
final Color? background = backgroundColor;
|
final MaterialStateProperty<Color?>? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) {
|
||||||
final Color? disabledBackground = disabledBackgroundColor;
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? backgroundColorProp = (background == null && disabledBackground == null)
|
(_, _) => _ElevatedButtonDefaultColor(foregroundColor, disabledForegroundColor),
|
||||||
? null
|
};
|
||||||
: _ElevatedButtonDefaultColor(background, disabledBackground);
|
final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
|
||||||
final Color? foreground = foregroundColor;
|
(null, null) => null,
|
||||||
final Color? disabledForeground = disabledForegroundColor;
|
(_, _) => _ElevatedButtonDefaultColor(backgroundColor, disabledBackgroundColor),
|
||||||
final MaterialStateProperty<Color?>? foregroundColorProp = (foreground == null && disabledForeground == null)
|
};
|
||||||
? null
|
final MaterialStateProperty<Color?>? iconColorProp = switch ((iconColor, disabledIconColor)) {
|
||||||
: _ElevatedButtonDefaultColor(foreground, disabledForeground);
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? overlayColor = (foreground == null)
|
(_, _) => _ElevatedButtonDefaultColor(iconColor, disabledIconColor),
|
||||||
? null
|
};
|
||||||
: _ElevatedButtonDefaultOverlay(foreground);
|
final MaterialStateProperty<Color?>? overlayColorProp = switch ((foregroundColor, overlayColor)) {
|
||||||
final MaterialStateProperty<double>? elevationValue = (elevation == null)
|
(null, null) => null,
|
||||||
? null
|
(_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll<Color?>(Colors.transparent),
|
||||||
: _ElevatedButtonDefaultElevation(elevation);
|
(_, _) => _ElevatedButtonDefaultOverlay((overlayColor ?? foregroundColor)!),
|
||||||
|
};
|
||||||
|
final MaterialStateProperty<double>? elevationValue = switch (elevation) {
|
||||||
|
null => null,
|
||||||
|
_ => _ElevatedButtonDefaultElevation(elevation),
|
||||||
|
};
|
||||||
final MaterialStateProperty<MouseCursor?> mouseCursor = _ElevatedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
final MaterialStateProperty<MouseCursor?> mouseCursor = _ElevatedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
||||||
|
|
||||||
return ButtonStyle(
|
return ButtonStyle(
|
||||||
textStyle: MaterialStatePropertyAll<TextStyle?>(textStyle),
|
textStyle: MaterialStatePropertyAll<TextStyle?>(textStyle),
|
||||||
backgroundColor: backgroundColorProp,
|
backgroundColor: backgroundColorProp,
|
||||||
foregroundColor: foregroundColorProp,
|
foregroundColor: foregroundColorProp,
|
||||||
overlayColor: overlayColor,
|
overlayColor: overlayColorProp,
|
||||||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||||
|
iconColor: iconColorProp,
|
||||||
elevation: elevationValue,
|
elevation: elevationValue,
|
||||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||||
@ -242,6 +261,8 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
splashFactory: splashFactory,
|
splashFactory: splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +304,7 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
/// * others - Theme.colorScheme.onPrimary
|
/// * others - Theme.colorScheme.onPrimary
|
||||||
/// * `overlayColor`
|
/// * `overlayColor`
|
||||||
/// * hovered - Theme.colorScheme.onPrimary(0.08)
|
/// * hovered - Theme.colorScheme.onPrimary(0.08)
|
||||||
/// * focused or pressed - Theme.colorScheme.onPrimary(0.24)
|
/// * focused or pressed - Theme.colorScheme.onPrimary(0.12)
|
||||||
/// * `shadowColor` - Theme.shadowColor
|
/// * `shadowColor` - Theme.shadowColor
|
||||||
/// * `elevation`
|
/// * `elevation`
|
||||||
/// * disabled - 0
|
/// * disabled - 0
|
||||||
@ -507,13 +528,12 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
clipBehavior: clipBehavior ?? Clip.none,
|
|
||||||
child: _ElevatedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _ElevatedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -202,19 +202,23 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
/// A static convenience method that constructs a filled button
|
/// A static convenience method that constructs a filled button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
///
|
///
|
||||||
/// The [foregroundColor], and [disabledForegroundColor] colors are used to create a
|
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
||||||
/// [MaterialStateProperty] [ButtonStyle.foregroundColor] value. The
|
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
||||||
/// [backgroundColor] and [disabledBackgroundColor] are used to create a
|
/// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified.
|
||||||
/// [MaterialStateProperty] [ButtonStyle.backgroundColor] value.
|
///
|
||||||
|
/// If [overlayColor] is specified and its value is [Colors.transparent]
|
||||||
|
/// then the pressed/focused/hovered highlights are effectively defeated.
|
||||||
|
/// Otherwise a [MaterialStateProperty] with the same opacities as the
|
||||||
|
/// default is created.
|
||||||
|
///
|
||||||
|
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||||||
|
/// parameters are used to construct [ButtonStyle.mouseCursor].
|
||||||
///
|
///
|
||||||
/// The button's elevations are defined relative to the [elevation]
|
/// The button's elevations are defined relative to the [elevation]
|
||||||
/// parameter. The disabled elevation is the same as the parameter
|
/// parameter. The disabled elevation is the same as the parameter
|
||||||
/// value, [elevation] + 2 is used when the button is hovered
|
/// value, [elevation] + 2 is used when the button is hovered
|
||||||
/// or focused, and elevation + 6 is used when the button is pressed.
|
/// or focused, and elevation + 6 is used when the button is pressed.
|
||||||
///
|
///
|
||||||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
|
||||||
/// parameters are used to construct [ButtonStyle.mouseCursor].
|
|
||||||
///
|
|
||||||
/// All of the other parameters are either used directly or used to
|
/// All of the other parameters are either used directly or used to
|
||||||
/// create a [MaterialStateProperty] with a single value for all
|
/// create a [MaterialStateProperty] with a single value for all
|
||||||
/// states.
|
/// states.
|
||||||
@ -250,6 +254,9 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
Color? disabledBackgroundColor,
|
Color? disabledBackgroundColor,
|
||||||
Color? shadowColor,
|
Color? shadowColor,
|
||||||
Color? surfaceTintColor,
|
Color? surfaceTintColor,
|
||||||
|
Color? iconColor,
|
||||||
|
Color? disabledIconColor,
|
||||||
|
Color? overlayColor,
|
||||||
double? elevation,
|
double? elevation,
|
||||||
TextStyle? textStyle,
|
TextStyle? textStyle,
|
||||||
EdgeInsetsGeometry? padding,
|
EdgeInsetsGeometry? padding,
|
||||||
@ -266,29 +273,36 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
bool? enableFeedback,
|
bool? enableFeedback,
|
||||||
AlignmentGeometry? alignment,
|
AlignmentGeometry? alignment,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
|
ButtonLayerBuilder? backgroundBuilder,
|
||||||
|
ButtonLayerBuilder? foregroundBuilder,
|
||||||
}) {
|
}) {
|
||||||
final MaterialStateProperty<Color?>? backgroundColorProp =
|
final MaterialStateProperty<Color?>? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) {
|
||||||
(backgroundColor == null && disabledBackgroundColor == null)
|
(null, null) => null,
|
||||||
? null
|
(_, _) => _FilledButtonDefaultColor(foregroundColor, disabledForegroundColor),
|
||||||
: _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor);
|
};
|
||||||
final Color? foreground = foregroundColor;
|
final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
|
||||||
final Color? disabledForeground = disabledForegroundColor;
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? foregroundColorProp =
|
(_, _) => _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor),
|
||||||
(foreground == null && disabledForeground == null)
|
};
|
||||||
? null
|
final MaterialStateProperty<Color?>? iconColorProp = switch ((iconColor, disabledIconColor)) {
|
||||||
: _FilledButtonDefaultColor(foreground, disabledForeground);
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? overlayColor = (foreground == null)
|
(_, _) => _FilledButtonDefaultColor(iconColor, disabledIconColor),
|
||||||
? null
|
};
|
||||||
: _FilledButtonDefaultOverlay(foreground);
|
final MaterialStateProperty<Color?>? overlayColorProp = switch ((foregroundColor, overlayColor)) {
|
||||||
|
(null, null) => null,
|
||||||
|
(_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll<Color?>(Colors.transparent),
|
||||||
|
(_, _) => _FilledButtonDefaultOverlay((overlayColor ?? foregroundColor)!),
|
||||||
|
};
|
||||||
final MaterialStateProperty<MouseCursor?> mouseCursor = _FilledButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
final MaterialStateProperty<MouseCursor?> mouseCursor = _FilledButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
||||||
|
|
||||||
return ButtonStyle(
|
return ButtonStyle(
|
||||||
textStyle: MaterialStatePropertyAll<TextStyle?>(textStyle),
|
textStyle: MaterialStatePropertyAll<TextStyle?>(textStyle),
|
||||||
backgroundColor: backgroundColorProp,
|
backgroundColor: backgroundColorProp,
|
||||||
foregroundColor: foregroundColorProp,
|
foregroundColor: foregroundColorProp,
|
||||||
overlayColor: overlayColor,
|
overlayColor: overlayColorProp,
|
||||||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||||
|
iconColor: iconColorProp,
|
||||||
elevation: ButtonStyleButton.allOrNull(elevation),
|
elevation: ButtonStyleButton.allOrNull(elevation),
|
||||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||||
@ -303,6 +317,8 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
splashFactory: splashFactory,
|
splashFactory: splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,14 +531,13 @@ class _FilledButtonWithIcon extends FilledButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
clipBehavior: clipBehavior ?? Clip.none,
|
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
|
||||||
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_FilledButtonWithIcon.tonal({
|
_FilledButtonWithIcon.tonal({
|
||||||
@ -534,14 +549,13 @@ class _FilledButtonWithIcon extends FilledButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) : super.tonal(
|
}) : super.tonal(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
clipBehavior: clipBehavior ?? Clip.none,
|
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style)
|
||||||
child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -168,6 +168,9 @@ abstract class MaterialStateColor extends Color implements MaterialStateProperty
|
|||||||
/// specified state.
|
/// specified state.
|
||||||
@override
|
@override
|
||||||
Color resolve(Set<MaterialState> states);
|
Color resolve(Set<MaterialState> states);
|
||||||
|
|
||||||
|
/// A constant whose value is [Colors.transparent] for all states.
|
||||||
|
static const MaterialStateColor transparent = _MaterialStateColorTransparent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [MaterialStateColor] created from a [MaterialPropertyResolver<Color>]
|
/// A [MaterialStateColor] created from a [MaterialPropertyResolver<Color>]
|
||||||
@ -189,6 +192,13 @@ class _MaterialStateColor extends MaterialStateColor {
|
|||||||
Color resolve(Set<MaterialState> states) => _resolve(states);
|
Color resolve(Set<MaterialState> states) => _resolve(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _MaterialStateColorTransparent extends MaterialStateColor {
|
||||||
|
const _MaterialStateColorTransparent() : super(0x00000000);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color resolve(Set<MaterialState> states) => const Color(0x00000000);
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which
|
/// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which
|
||||||
/// represent the interactive state of a component.
|
/// represent the interactive state of a component.
|
||||||
///
|
///
|
||||||
|
@ -75,7 +75,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
super.autofocus = false,
|
super.autofocus = false,
|
||||||
super.clipBehavior = Clip.none,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required super.child,
|
required super.child,
|
||||||
});
|
});
|
||||||
@ -129,16 +129,22 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
/// A static convenience method that constructs an outlined button
|
/// A static convenience method that constructs an outlined button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
///
|
///
|
||||||
///
|
|
||||||
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
||||||
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
||||||
/// a derived [ButtonStyle.overlayColor].
|
/// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified.
|
||||||
///
|
///
|
||||||
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
||||||
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
||||||
///
|
///
|
||||||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||||||
/// parameters are used to construct [ButtonStyle.mouseCursor].
|
/// parameters are used to construct [ButtonStyle.mouseCursor] and
|
||||||
|
/// [iconColor], [disabledIconColor] are used to construct
|
||||||
|
/// [ButtonStyle.iconColor].
|
||||||
|
///
|
||||||
|
/// If [overlayColor] is specified and its value is [Colors.transparent]
|
||||||
|
/// then the pressed/focused/hovered highlights are effectively defeated.
|
||||||
|
/// Otherwise a [MaterialStateProperty] with the same opacities as the
|
||||||
|
/// default is created.
|
||||||
///
|
///
|
||||||
/// All of the other parameters are either used directly or used to
|
/// All of the other parameters are either used directly or used to
|
||||||
/// create a [MaterialStateProperty] with a single value for all
|
/// create a [MaterialStateProperty] with a single value for all
|
||||||
@ -169,6 +175,9 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
Color? disabledBackgroundColor,
|
Color? disabledBackgroundColor,
|
||||||
Color? shadowColor,
|
Color? shadowColor,
|
||||||
Color? surfaceTintColor,
|
Color? surfaceTintColor,
|
||||||
|
Color? iconColor,
|
||||||
|
Color? disabledIconColor,
|
||||||
|
Color? overlayColor,
|
||||||
double? elevation,
|
double? elevation,
|
||||||
TextStyle? textStyle,
|
TextStyle? textStyle,
|
||||||
EdgeInsetsGeometry? padding,
|
EdgeInsetsGeometry? padding,
|
||||||
@ -185,29 +194,37 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
bool? enableFeedback,
|
bool? enableFeedback,
|
||||||
AlignmentGeometry? alignment,
|
AlignmentGeometry? alignment,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
|
ButtonLayerBuilder? backgroundBuilder,
|
||||||
|
ButtonLayerBuilder? foregroundBuilder,
|
||||||
}) {
|
}) {
|
||||||
final Color? foreground = foregroundColor;
|
final MaterialStateProperty<Color?>? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) {
|
||||||
final Color? disabledForeground = disabledForegroundColor;
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? foregroundColorProp = (foreground == null && disabledForeground == null)
|
(_, _) => _OutlinedButtonDefaultColor(foregroundColor, disabledForegroundColor),
|
||||||
? null
|
};
|
||||||
: _OutlinedButtonDefaultColor(foreground, disabledForeground);
|
final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
|
||||||
final MaterialStateProperty<Color?>? backgroundColorProp = (backgroundColor == null && disabledBackgroundColor == null)
|
(null, null) => null,
|
||||||
? null
|
(_, null) => MaterialStatePropertyAll<Color?>(backgroundColor),
|
||||||
: disabledBackgroundColor == null
|
(_, _) => _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor),
|
||||||
? ButtonStyleButton.allOrNull<Color?>(backgroundColor)
|
};
|
||||||
: _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor);
|
final MaterialStateProperty<Color?>? iconColorProp = switch ((iconColor, disabledIconColor)) {
|
||||||
final MaterialStateProperty<Color?>? overlayColor = (foreground == null)
|
(null, null) => null,
|
||||||
? null
|
(_, _) => _OutlinedButtonDefaultColor(iconColor, disabledIconColor),
|
||||||
: _OutlinedButtonDefaultOverlay(foreground);
|
};
|
||||||
|
final MaterialStateProperty<Color?>? overlayColorProp = switch ((foregroundColor, overlayColor)) {
|
||||||
|
(null, null) => null,
|
||||||
|
(_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll<Color?>(Colors.transparent),
|
||||||
|
(_, _) => _OutlinedButtonDefaultOverlay((overlayColor ?? foregroundColor)!),
|
||||||
|
};
|
||||||
final MaterialStateProperty<MouseCursor?> mouseCursor = _OutlinedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
final MaterialStateProperty<MouseCursor?> mouseCursor = _OutlinedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
||||||
|
|
||||||
return ButtonStyle(
|
return ButtonStyle(
|
||||||
textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
|
textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
|
||||||
foregroundColor: foregroundColorProp,
|
foregroundColor: foregroundColorProp,
|
||||||
backgroundColor: backgroundColorProp,
|
backgroundColor: backgroundColorProp,
|
||||||
overlayColor: overlayColor,
|
overlayColor: overlayColorProp,
|
||||||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||||
|
iconColor: iconColorProp,
|
||||||
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
||||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||||
@ -222,6 +239,8 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
splashFactory: splashFactory,
|
splashFactory: splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +275,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
||||||
/// * others - Theme.colorScheme.primary
|
/// * others - Theme.colorScheme.primary
|
||||||
/// * `overlayColor`
|
/// * `overlayColor`
|
||||||
/// * hovered - Theme.colorScheme.primary(0.04)
|
/// * hovered - Theme.colorScheme.primary(0.08)
|
||||||
/// * focused or pressed - Theme.colorScheme.primary(0.12)
|
/// * focused or pressed - Theme.colorScheme.primary(0.12)
|
||||||
/// * `shadowColor` - Theme.shadowColor
|
/// * `shadowColor` - Theme.shadowColor
|
||||||
/// * `elevation` - 0
|
/// * `elevation` - 0
|
||||||
@ -339,9 +358,7 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
padding: _scaledPadding(context),
|
padding: _scaledPadding(context),
|
||||||
minimumSize: const Size(64, 36),
|
minimumSize: const Size(64, 36),
|
||||||
maximumSize: Size.infinite,
|
maximumSize: Size.infinite,
|
||||||
side: BorderSide(
|
side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)),
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
|
|
||||||
),
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
|
||||||
enabledMouseCursor: SystemMouseCursors.click,
|
enabledMouseCursor: SystemMouseCursors.click,
|
||||||
disabledMouseCursor: SystemMouseCursors.basic,
|
disabledMouseCursor: SystemMouseCursors.basic,
|
||||||
@ -434,13 +451,12 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
clipBehavior: clipBehavior ?? Clip.none,
|
|
||||||
child: _OutlinedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _OutlinedButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -50,8 +50,9 @@ import 'theme_data.dart';
|
|||||||
/// button will be disabled, it will not react to touch.
|
/// button will be disabled, it will not react to touch.
|
||||||
///
|
///
|
||||||
/// {@tool dartpad}
|
/// {@tool dartpad}
|
||||||
/// This sample shows how to render a disabled TextButton, an enabled TextButton
|
/// This sample shows various ways to configure TextButtons, from the
|
||||||
/// and lastly a TextButton with gradient background.
|
/// simplest default appearance to versions that don't resemble
|
||||||
|
/// Material Design at all.
|
||||||
///
|
///
|
||||||
/// ** See code in examples/api/lib/material/text_button/text_button.0.dart **
|
/// ** See code in examples/api/lib/material/text_button/text_button.0.dart **
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
@ -82,7 +83,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
super.autofocus = false,
|
super.autofocus = false,
|
||||||
super.clipBehavior = Clip.none,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
super.isSemanticButton,
|
super.isSemanticButton,
|
||||||
required Widget super.child,
|
required Widget super.child,
|
||||||
@ -142,13 +143,20 @@ class TextButton extends ButtonStyleButton {
|
|||||||
///
|
///
|
||||||
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
||||||
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
||||||
/// a derived [ButtonStyle.overlayColor].
|
/// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified.
|
||||||
///
|
///
|
||||||
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
/// The [backgroundColor] and [disabledBackgroundColor] colors are
|
||||||
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
|
||||||
///
|
///
|
||||||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||||||
/// parameters are used to construct [ButtonStyle.mouseCursor].
|
/// parameters are used to construct [ButtonStyle.mouseCursor] and
|
||||||
|
/// [iconColor], [disabledIconColor] are used to construct
|
||||||
|
/// [ButtonStyle.iconColor].
|
||||||
|
///
|
||||||
|
/// If [overlayColor] is specified and its value is [Colors.transparent]
|
||||||
|
/// then the pressed/focused/hovered highlights are effectively defeated.
|
||||||
|
/// Otherwise a [MaterialStateProperty] with the same opacities as the
|
||||||
|
/// default is created.
|
||||||
///
|
///
|
||||||
/// All of the other parameters are either used directly or used to
|
/// All of the other parameters are either used directly or used to
|
||||||
/// create a [MaterialStateProperty] with a single value for all
|
/// create a [MaterialStateProperty] with a single value for all
|
||||||
@ -180,6 +188,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
Color? surfaceTintColor,
|
Color? surfaceTintColor,
|
||||||
Color? iconColor,
|
Color? iconColor,
|
||||||
Color? disabledIconColor,
|
Color? disabledIconColor,
|
||||||
|
Color? overlayColor,
|
||||||
double? elevation,
|
double? elevation,
|
||||||
TextStyle? textStyle,
|
TextStyle? textStyle,
|
||||||
EdgeInsetsGeometry? padding,
|
EdgeInsetsGeometry? padding,
|
||||||
@ -196,32 +205,35 @@ class TextButton extends ButtonStyleButton {
|
|||||||
bool? enableFeedback,
|
bool? enableFeedback,
|
||||||
AlignmentGeometry? alignment,
|
AlignmentGeometry? alignment,
|
||||||
InteractiveInkFeatureFactory? splashFactory,
|
InteractiveInkFeatureFactory? splashFactory,
|
||||||
|
ButtonLayerBuilder? backgroundBuilder,
|
||||||
|
ButtonLayerBuilder? foregroundBuilder,
|
||||||
}) {
|
}) {
|
||||||
final Color? foreground = foregroundColor;
|
final MaterialStateProperty<Color?>? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) {
|
||||||
final Color? disabledForeground = disabledForegroundColor;
|
(null, null) => null,
|
||||||
final MaterialStateProperty<Color?>? foregroundColorProp = (foreground == null && disabledForeground == null)
|
(_, _) => _TextButtonDefaultColor(foregroundColor, disabledForegroundColor),
|
||||||
? null
|
};
|
||||||
: _TextButtonDefaultColor(foreground, disabledForeground);
|
final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
|
||||||
final MaterialStateProperty<Color?>? backgroundColorProp = (backgroundColor == null && disabledBackgroundColor == null)
|
(null, null) => null,
|
||||||
? null
|
(_, null) => MaterialStatePropertyAll<Color?>(backgroundColor),
|
||||||
: disabledBackgroundColor == null
|
(_, _) => _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor),
|
||||||
? ButtonStyleButton.allOrNull<Color?>(backgroundColor)
|
};
|
||||||
: _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor);
|
final MaterialStateProperty<Color?>? iconColorProp = switch ((iconColor, disabledIconColor)) {
|
||||||
final MaterialStateProperty<Color?>? overlayColor = (foreground == null)
|
(null, null) => null,
|
||||||
? null
|
(_, null) => MaterialStatePropertyAll<Color?>(iconColor),
|
||||||
: _TextButtonDefaultOverlay(foreground);
|
(_, _) => _TextButtonDefaultColor(iconColor, disabledIconColor),
|
||||||
final MaterialStateProperty<Color?>? iconColorProp = (iconColor == null && disabledIconColor == null)
|
};
|
||||||
? null
|
final MaterialStateProperty<Color?>? overlayColorProp = switch ((foregroundColor, overlayColor)) {
|
||||||
: disabledIconColor == null
|
(null, null) => null,
|
||||||
? ButtonStyleButton.allOrNull<Color?>(iconColor)
|
(_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll<Color?>(Colors.transparent),
|
||||||
: _TextButtonDefaultIconColor(iconColor, disabledIconColor);
|
(_, _) => _TextButtonDefaultOverlay((overlayColor ?? foregroundColor)!),
|
||||||
|
};
|
||||||
final MaterialStateProperty<MouseCursor?> mouseCursor = _TextButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
final MaterialStateProperty<MouseCursor?> mouseCursor = _TextButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
||||||
|
|
||||||
return ButtonStyle(
|
return ButtonStyle(
|
||||||
textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
|
textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
|
||||||
foregroundColor: foregroundColorProp,
|
foregroundColor: foregroundColorProp,
|
||||||
backgroundColor: backgroundColorProp,
|
backgroundColor: backgroundColorProp,
|
||||||
overlayColor: overlayColor,
|
overlayColor: overlayColorProp,
|
||||||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||||
iconColor: iconColorProp,
|
iconColor: iconColorProp,
|
||||||
@ -239,6 +251,8 @@ class TextButton extends ButtonStyleButton {
|
|||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
splashFactory: splashFactory,
|
splashFactory: splashFactory,
|
||||||
|
backgroundBuilder: backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +293,7 @@ class TextButton extends ButtonStyleButton {
|
|||||||
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
||||||
/// * others - Theme.colorScheme.primary
|
/// * others - Theme.colorScheme.primary
|
||||||
/// * `overlayColor`
|
/// * `overlayColor`
|
||||||
/// * hovered - Theme.colorScheme.primary(0.04)
|
/// * hovered - Theme.colorScheme.primary(0.08)
|
||||||
/// * focused or pressed - Theme.colorScheme.primary(0.12)
|
/// * focused or pressed - Theme.colorScheme.primary(0.12)
|
||||||
/// * `shadowColor` - Theme.shadowColor
|
/// * `shadowColor` - Theme.shadowColor
|
||||||
/// * `elevation` - 0
|
/// * `elevation` - 0
|
||||||
@ -453,27 +467,6 @@ class _TextButtonDefaultOverlay extends MaterialStateProperty<Color?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
|
||||||
class _TextButtonDefaultIconColor extends MaterialStateProperty<Color?> {
|
|
||||||
_TextButtonDefaultIconColor(this.iconColor, this.disabledIconColor);
|
|
||||||
|
|
||||||
final Color? iconColor;
|
|
||||||
final Color? disabledIconColor;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color? resolve(Set<MaterialState> states) {
|
|
||||||
if (states.contains(MaterialState.disabled)) {
|
|
||||||
return disabledIconColor;
|
|
||||||
}
|
|
||||||
return iconColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return '{disabled: $disabledIconColor, color: $iconColor}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class _TextButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> with Diagnosticable {
|
class _TextButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> with Diagnosticable {
|
||||||
_TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
|
_TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
|
||||||
@ -500,13 +493,12 @@ class _TextButtonWithIcon extends TextButton {
|
|||||||
super.style,
|
super.style,
|
||||||
super.focusNode,
|
super.focusNode,
|
||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
super.clipBehavior,
|
||||||
super.statesController,
|
super.statesController,
|
||||||
required Widget icon,
|
required Widget icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) : super(
|
}) : super(
|
||||||
autofocus: autofocus ?? false,
|
autofocus: autofocus ?? false,
|
||||||
clipBehavior: clipBehavior ?? Clip.none,
|
|
||||||
child: _TextButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
child: _TextButtonWithIconChild(icon: icon, label: label, buttonStyle: style),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1981,6 +1981,203 @@ void main() {
|
|||||||
expect(controller.value, <MaterialState>{MaterialState.disabled});
|
expect(controller.value, <MaterialState>{MaterialState.disabled});
|
||||||
expect(count, 1);
|
expect(count, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ElevatedButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BoxDecoration boxDecorationOf(Finder finder) {
|
||||||
|
return tester.widget<DecoratedBox>(finder).decoration as BoxDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Finder decorations = find.descendant(
|
||||||
|
of: find.byType(ElevatedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(boxDecorationOf(decorations.at(0)).color, backgroundColor);
|
||||||
|
expect(boxDecorationOf(decorations.at(1)).color, foregroundColor);
|
||||||
|
|
||||||
|
Text textChildOf(Finder finder) {
|
||||||
|
return tester.widget<Text>(
|
||||||
|
find.descendant(
|
||||||
|
of: finder,
|
||||||
|
matching: find.byType(Text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(textChildOf(decorations.at(0)).data, 'button');
|
||||||
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('ElevatedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder background = find.descendant(
|
||||||
|
of: find.byType(ElevatedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(background, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ElevatedButton foregroundBuilder drops button child', (WidgetTester tester) async {
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder foreground = find.descendant(
|
||||||
|
of: find.byType(ElevatedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(foreground, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ElevatedButton foreground and background builders are applied to the correct states', (WidgetTester tester) async {
|
||||||
|
Set<MaterialState> foregroundStates = <MaterialState>{};
|
||||||
|
Set<MaterialState> backgroundStates = <MaterialState>{};
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
backgroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
foregroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default.
|
||||||
|
expect(backgroundStates.isEmpty, isTrue);
|
||||||
|
expect(foregroundStates.isEmpty, isTrue);
|
||||||
|
|
||||||
|
const Set<MaterialState> focusedStates = <MaterialState>{MaterialState.focused};
|
||||||
|
const Set<MaterialState> focusedHoveredStates = <MaterialState>{MaterialState.focused, MaterialState.hovered};
|
||||||
|
const Set<MaterialState> focusedHoveredPressedStates = <MaterialState>{MaterialState.focused, MaterialState.hovered, MaterialState.pressed};
|
||||||
|
|
||||||
|
bool sameStates(Set<MaterialState> expectedValue, Set<MaterialState> actualValue) {
|
||||||
|
return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
// Hovered.
|
||||||
|
final Offset center = tester.getCenter(find.byType(ElevatedButton));
|
||||||
|
final TestGesture gesture = await tester.createGesture(
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(center);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
|
// 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(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
focusNode.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -2092,6 +2092,202 @@ void main() {
|
|||||||
expect(count, 1);
|
expect(count, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('FilledButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BoxDecoration boxDecorationOf(Finder finder) {
|
||||||
|
return tester.widget<DecoratedBox>(finder).decoration as BoxDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Finder decorations = find.descendant(
|
||||||
|
of: find.byType(FilledButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(boxDecorationOf(decorations.at(0)).color, backgroundColor);
|
||||||
|
expect(boxDecorationOf(decorations.at(1)).color, foregroundColor);
|
||||||
|
|
||||||
|
Text textChildOf(Finder finder) {
|
||||||
|
return tester.widget<Text>(
|
||||||
|
find.descendant(
|
||||||
|
of: finder,
|
||||||
|
matching: find.byType(Text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(textChildOf(decorations.at(0)).data, 'button');
|
||||||
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('FilledButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder background = find.descendant(
|
||||||
|
of: find.byType(FilledButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(background, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('FilledButton foregroundBuilder drops button child', (WidgetTester tester) async {
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder foreground = find.descendant(
|
||||||
|
of: find.byType(FilledButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(foreground, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('FilledButton foreground and background builders are applied to the correct states', (WidgetTester tester) async {
|
||||||
|
Set<MaterialState> foregroundStates = <MaterialState>{};
|
||||||
|
Set<MaterialState> backgroundStates = <MaterialState>{};
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: FilledButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
backgroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
foregroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default.
|
||||||
|
expect(backgroundStates.isEmpty, isTrue);
|
||||||
|
expect(foregroundStates.isEmpty, isTrue);
|
||||||
|
|
||||||
|
const Set<MaterialState> focusedStates = <MaterialState>{MaterialState.focused};
|
||||||
|
const Set<MaterialState> focusedHoveredStates = <MaterialState>{MaterialState.focused, MaterialState.hovered};
|
||||||
|
const Set<MaterialState> focusedHoveredPressedStates = <MaterialState>{MaterialState.focused, MaterialState.hovered, MaterialState.pressed};
|
||||||
|
|
||||||
|
bool sameStates(Set<MaterialState> expectedValue, Set<MaterialState> actualValue) {
|
||||||
|
return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
// Hovered.
|
||||||
|
final Offset center = tester.getCenter(find.byType(FilledButton));
|
||||||
|
final TestGesture gesture = await tester.createGesture(
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(center);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
|
// 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(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
focusNode.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -2131,6 +2131,232 @@ void main() {
|
|||||||
|
|
||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BoxDecoration boxDecorationOf(Finder finder) {
|
||||||
|
return tester.widget<DecoratedBox>(finder).decoration as BoxDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Finder decorations = find.descendant(
|
||||||
|
of: find.byType(OutlinedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(boxDecorationOf(decorations.at(0)).color, backgroundColor);
|
||||||
|
expect(boxDecorationOf(decorations.at(1)).color, foregroundColor);
|
||||||
|
|
||||||
|
Text textChildOf(Finder finder) {
|
||||||
|
return tester.widget<Text>(
|
||||||
|
find.descendant(
|
||||||
|
of: finder,
|
||||||
|
matching: find.byType(Text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(textChildOf(decorations.at(0)).data, 'button');
|
||||||
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder background = find.descendant(
|
||||||
|
of: find.byType(OutlinedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(background, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton foregroundBuilder drops button child', (WidgetTester tester) async {
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder foreground = find.descendant(
|
||||||
|
of: find.byType(OutlinedButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(foreground, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton foreground and background builders are applied to the correct states', (WidgetTester tester) async {
|
||||||
|
Set<MaterialState> foregroundStates = <MaterialState>{};
|
||||||
|
Set<MaterialState> backgroundStates = <MaterialState>{};
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
backgroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
foregroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default.
|
||||||
|
expect(backgroundStates.isEmpty, isTrue);
|
||||||
|
expect(foregroundStates.isEmpty, isTrue);
|
||||||
|
|
||||||
|
const Set<MaterialState> focusedStates = <MaterialState>{MaterialState.focused};
|
||||||
|
const Set<MaterialState> focusedHoveredStates = <MaterialState>{MaterialState.focused, MaterialState.hovered};
|
||||||
|
const Set<MaterialState> focusedHoveredPressedStates = <MaterialState>{MaterialState.focused, MaterialState.hovered, MaterialState.pressed};
|
||||||
|
|
||||||
|
bool sameStates(Set<MaterialState> expectedValue, Set<MaterialState> actualValue) {
|
||||||
|
return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
// Hovered.
|
||||||
|
final Offset center = tester.getCenter(find.byType(OutlinedButton));
|
||||||
|
final TestGesture gesture = await tester.createGesture(
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(center);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
|
// 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(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
focusNode.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton styleFrom backgroundColor special case', (WidgetTester tester) async {
|
||||||
|
// Regression test for an internal Google issue: b/323399158
|
||||||
|
|
||||||
|
const Color backgroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
Widget buildFrame({ VoidCallback? onPressed }) {
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame(onPressed: () { })); // enabled
|
||||||
|
final Material material = tester.widget<Material>(find.descendant(
|
||||||
|
of: find.byType(OutlinedButton),
|
||||||
|
matching: find.byType(Material),
|
||||||
|
));
|
||||||
|
expect(material.color, backgroundColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
||||||
|
expect(material.color, backgroundColor);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -1964,6 +1964,232 @@ void main() {
|
|||||||
|
|
||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BoxDecoration boxDecorationOf(Finder finder) {
|
||||||
|
return tester.widget<DecoratedBox>(finder).decoration as BoxDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Finder decorations = find.descendant(
|
||||||
|
of: find.byType(TextButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(boxDecorationOf(decorations.at(0)).color, backgroundColor);
|
||||||
|
expect(boxDecorationOf(decorations.at(1)).color, foregroundColor);
|
||||||
|
|
||||||
|
Text textChildOf(Finder finder) {
|
||||||
|
return tester.widget<Text>(
|
||||||
|
find.descendant(
|
||||||
|
of: finder,
|
||||||
|
matching: find.byType(Text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(textChildOf(decorations.at(0)).data, 'button');
|
||||||
|
expect(textChildOf(decorations.at(1)).data, 'button');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('TextButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async {
|
||||||
|
const Color backgroundColor = Color(0xFF000011);
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder background = find.descendant(
|
||||||
|
of: find.byType(TextButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(background, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButton foregroundBuilder drops button child', (WidgetTester tester) async {
|
||||||
|
const Color foregroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: foregroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder foreground = find.descendant(
|
||||||
|
of: find.byType(TextButton),
|
||||||
|
matching: find.byType(DecoratedBox),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(foreground, findsOneWidget);
|
||||||
|
expect(find.text('button'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButton foreground and background builders are applied to the correct states', (WidgetTester tester) async {
|
||||||
|
Set<MaterialState> foregroundStates = <MaterialState>{};
|
||||||
|
Set<MaterialState> backgroundStates = <MaterialState>{};
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
backgroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
foregroundStates = states;
|
||||||
|
return child!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default.
|
||||||
|
expect(backgroundStates.isEmpty, isTrue);
|
||||||
|
expect(foregroundStates.isEmpty, isTrue);
|
||||||
|
|
||||||
|
const Set<MaterialState> focusedStates = <MaterialState>{MaterialState.focused};
|
||||||
|
const Set<MaterialState> focusedHoveredStates = <MaterialState>{MaterialState.focused, MaterialState.hovered};
|
||||||
|
const Set<MaterialState> focusedHoveredPressedStates = <MaterialState>{MaterialState.focused, MaterialState.hovered, MaterialState.pressed};
|
||||||
|
|
||||||
|
bool sameStates(Set<MaterialState> expectedValue, Set<MaterialState> actualValue) {
|
||||||
|
return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused.
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
// Hovered.
|
||||||
|
final Offset center = tester.getCenter(find.byType(TextButton));
|
||||||
|
final TestGesture gesture = await tester.createGesture(
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(center);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(sameStates(focusedHoveredStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
|
||||||
|
// 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(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue);
|
||||||
|
expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue);
|
||||||
|
|
||||||
|
focusNode.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButton styleFrom backgroundColor special case', (WidgetTester tester) async {
|
||||||
|
// Regression test for an internal Google issue: b/323399158
|
||||||
|
|
||||||
|
const Color backgroundColor = Color(0xFF000022);
|
||||||
|
|
||||||
|
Widget buildFrame({ VoidCallback? onPressed }) {
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
),
|
||||||
|
onPressed: () { },
|
||||||
|
child: const Text('button'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame(onPressed: () { })); // enabled
|
||||||
|
final Material material = tester.widget<Material>(find.descendant(
|
||||||
|
of: find.byType(TextButton),
|
||||||
|
matching: find.byType(Material),
|
||||||
|
));
|
||||||
|
expect(material.color, backgroundColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame()); // onPressed: null - disabled
|
||||||
|
expect(material.color, backgroundColor);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
|
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
|
||||||
|
@ -104,6 +104,15 @@ void main() {
|
|||||||
const bool enableFeedback = false;
|
const bool enableFeedback = false;
|
||||||
const AlignmentGeometry alignment = Alignment.centerLeft;
|
const AlignmentGeometry alignment = Alignment.centerLeft;
|
||||||
|
|
||||||
|
final Key backgroundKey = UniqueKey();
|
||||||
|
final Key foregroundKey = UniqueKey();
|
||||||
|
Widget backgroundBuilder(BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return KeyedSubtree(key: backgroundKey, child: child!);
|
||||||
|
}
|
||||||
|
Widget foregroundBuilder(BuildContext context, Set<MaterialState> states, Widget? child) {
|
||||||
|
return KeyedSubtree(key: foregroundKey, child: child!);
|
||||||
|
}
|
||||||
|
|
||||||
final ButtonStyle style = TextButton.styleFrom(
|
final ButtonStyle style = TextButton.styleFrom(
|
||||||
foregroundColor: foregroundColor,
|
foregroundColor: foregroundColor,
|
||||||
disabledForegroundColor: disabledColor,
|
disabledForegroundColor: disabledColor,
|
||||||
@ -122,6 +131,8 @@ void main() {
|
|||||||
animationDuration: animationDuration,
|
animationDuration: animationDuration,
|
||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
|
backgroundBuilder: backgroundBuilder,
|
||||||
|
foregroundBuilder: foregroundBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget buildFrame({ ButtonStyle? buttonStyle, ButtonStyle? themeStyle, ButtonStyle? overallStyle }) {
|
Widget buildFrame({ ButtonStyle? buttonStyle, ButtonStyle? themeStyle, ButtonStyle? overallStyle }) {
|
||||||
@ -185,6 +196,8 @@ void main() {
|
|||||||
expect(tester.getSize(find.byType(TextButton)), const Size(200, 200));
|
expect(tester.getSize(find.byType(TextButton)), const Size(200, 200));
|
||||||
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align)));
|
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align)));
|
||||||
expect(align.alignment, alignment);
|
expect(align.alignment, alignment);
|
||||||
|
expect(find.descendant(of: findMaterial, matching: find.byKey(backgroundKey)), findsOneWidget);
|
||||||
|
expect(find.descendant(of: findInkWell, matching: find.byKey(foregroundKey)), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('Button style overrides defaults', (WidgetTester tester) async {
|
testWidgets('Button style overrides defaults', (WidgetTester tester) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user