New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder #141818
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The examples in the PR description look very cool!
Mostly just doc nits asking for more context.
/// and whose child is the rest of the button, including the button's | ||
/// `child` parameter. | ||
/// | ||
/// By default the returned widget is clipped to the Material's [ButtonStyle.shape]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to include some of the examples from the PR's description here and on the other builder to show how powerful these are and what one can do with them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably also include a note about making sure that highlights come through by either using a non-opaque background color or the Ink widget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely planning to write more tests and to add API doc examples, yes!
7b94242
to
41dbd62
Compare
41dbd62
to
2b0b1ec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some doc nits.
final Color color1; | ||
final Color color2; | ||
final Color color3; | ||
if (colorScheme.brightness == Brightness.light) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should probably be a switch? https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-using-if-chains-or--or--with-enum-values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes!
// This theme defines default property overrides for all of the buttons | ||
// that follow. | ||
TextButtonTheme | ||
( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting here is odd, this (
should probably be on the previous line?
), | ||
verticalSpacer, | ||
|
||
// Override button's shape its border. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something seems to be missing in this sentence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, thanks for catching that.
// Override the foregroundBuilder to specify images for the button's pressed | ||
// hovered and inactive states. | ||
// | ||
// This is an example of completely change the default appearance of a button |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change -> changing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, fixed.
// overlayColor: Colors.transparent. AnimatedContainer takes care of the | ||
// fade in and out segues between images. | ||
// | ||
// The foregroundBuilder its child parameter. Unfortunately TextButton's child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what "The foregroundBuilder its child parameter" means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have been // This foregroundBuilder function ignores its child parameter.
. Fixed. Thanks for catching that.
/// instead of the button's child. | ||
/// | ||
/// The returned widget is clipped by the button's | ||
/// [ButtonStyle.shape] inset by the button's [ButtonStyle.padding] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should there be a comma before inset?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Thanks and fixed.
/// | ||
/// The [backgroundColor] and [disabledBackgroundColor] colors are | ||
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. | ||
/// | ||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] | ||
/// parameters are used to construct [ButtonStyle].mouseCursor and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
/// parameters are used to construct [ButtonStyle].mouseCursor and | |
/// parameters are used to construct [ButtonStyle.mouseCursor] and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, fixed.
/// | ||
/// The [backgroundColor] and [disabledBackgroundColor] colors are | ||
/// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. | ||
/// | ||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] | ||
/// parameters are used to construct [ButtonStyle.mouseCursor]. | ||
/// parameters are used to construct [ButtonStyle].mouseCursor and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// parameters are used to construct [ButtonStyle].mouseCursor and | |
/// parameters are used to construct [ButtonStyle.mouseCursor] and |
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was consistently wrong, thanks for catching that.
2b0b1ec
to
146e2bb
Compare
final Color color1; | ||
final Color color2; | ||
final Color color3; | ||
switch (colorScheme.brightness) { | ||
case Brightness.light: | ||
color1 = Colors.blue.withOpacity(0.5); | ||
color2 = Colors.orange.withOpacity(0.5); | ||
color3 = Colors.yellow.withOpacity(0.5); | ||
case Brightness.dark: | ||
color1 = Colors.purple.withOpacity(0.5); | ||
color2 = Colors.cyan.withOpacity(0.5); | ||
color3 = Colors.yellow.withOpacity(0.5); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final Color color1; | |
final Color color2; | |
final Color color3; | |
switch (colorScheme.brightness) { | |
case Brightness.light: | |
color1 = Colors.blue.withOpacity(0.5); | |
color2 = Colors.orange.withOpacity(0.5); | |
color3 = Colors.yellow.withOpacity(0.5); | |
case Brightness.dark: | |
color1 = Colors.purple.withOpacity(0.5); | |
color2 = Colors.cyan.withOpacity(0.5); | |
color3 = Colors.yellow.withOpacity(0.5); | |
} | |
final (Color color1, Color color2, Color color3) = switch (colorScheme.brightness) { | |
Brightness.light => ( | |
Colors.blue.withOpacity(0.5), | |
Colors.orange.withOpacity(0.5), | |
Colors.yellow.withOpacity(0.5), | |
), | |
Brightness.dark => ( | |
Colors.purple.withOpacity(0.5), | |
Colors.cyan.withOpacity(0.5), | |
Colors.yellow.withOpacity(0.5), | |
), | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so much better. It's hard (It was hard for me) to find an example of this syntax in action. Hopefully we're helping spread the word.
146e2bb
to
2d8842d
Compare
5898592
to
4881a5b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
super.dispose(); | ||
scrollController.dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super.dispose(); | |
scrollController.dispose(); | |
scrollController.dispose(); | |
super.dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
]; | ||
|
||
|
||
final List<Widget> columnTwoButtons = <Widget>[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
]; | |
final List<Widget> columnTwoButtons = <Widget>[ | |
]; | |
final List<Widget> columnTwoButtons = <Widget>[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
c3adc86
to
f5e8afc
Compare
auto label is removed for flutter/flutter/141818, due to - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
…6028) Manual roll Flutter from c65ab4d513da to e02e2079bea7 (38 revisions) Manual roll requested by stuartmorgan@google.com flutter/flutter@c65ab4d...e02e207 2024-02-01 engine-flutter-autoroll@skia.org Roll Flutter Engine from f4fbabf1eb9f to 68943afd62d1 (9 revisions) (flutter/flutter#142690) 2024-02-01 36861262+QuncCccccc@users.noreply.github.com Introduce tone-based surfaces and accent color add-ons - Part 1 (flutter/flutter#142654) 2024-02-01 andrewrkolos@gmail.com improve error message when `--base-href` argument does not start with `/` (flutter/flutter#142667) 2024-02-01 engine-flutter-autoroll@skia.org Roll Flutter Engine from c4247c5e31ba to f4fbabf1eb9f (1 revision) (flutter/flutter#142675) 2024-02-01 engine-flutter-autoroll@skia.org Roll Flutter Engine from c83617eee093 to c4247c5e31ba (3 revisions) (flutter/flutter#142662) 2024-02-01 jonahwilliams@google.com [Impeller] opt vulkan tests into GPU tracing. (flutter/flutter#142649) 2024-02-01 gspencergoog@users.noreply.github.com Convert button `.icon` and `.tonalIcon` constructors to take nullable icons. (flutter/flutter#142644) 2024-02-01 davidmartos96@gmail.com Fix token usages on Regular Chip and Action Chip (flutter/flutter#141701) 2024-02-01 hans.muller@gmail.com Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (flutter/flutter#141818) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from 5b89189b8b5f to c83617eee093 (2 revisions) (flutter/flutter#142656) 2024-01-31 christopherfujino@gmail.com [flutter_tools] add debugging to ios/core_devices.dart (flutter/flutter#142187) 2024-01-31 gspencergoog@users.noreply.github.com Fix showDialog docs (flutter/flutter#142458) 2024-01-31 49699333+dependabot[bot]@users.noreply.github.com Bump peter-evans/create-pull-request from 5.0.2 to 6.0.0 (flutter/flutter#142650) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from 20e53614c16c to 5b89189b8b5f (2 revisions) (flutter/flutter#142640) 2024-01-31 dnfield@google.com Refactor ShaderTarget to not explicitly mention impeller or Skia (flutter/flutter#141460) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from 9ccd81d7595b to 20e53614c16c (3 revisions) (flutter/flutter#142628) 2024-01-31 louisehsu@google.com Show Mac Designed For iPad in 'flutter devices' (flutter/flutter#141718) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_arm64_ios basic_material_app_ios__compile to be unflaky (flutter/flutter#142594) 2024-01-31 goderbauer@google.com Fix ParentDataWidget crash for multi view scenarios (flutter/flutter#142486) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from e0d8f472a1b6 to 9ccd81d7595b (1 revision) (flutter/flutter#142625) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_arm64 tool_tests_commands to be unflaky (flutter/flutter#142593) 2024-01-31 jmccandless@google.com "System back gesture" explanation (flutter/flutter#142254) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_x64 tool_tests_commands to be unflaky (flutter/flutter#142592) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_x64_ios integration_test_test_ios to be unflaky (flutter/flutter#142595) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_x64 native_ui_tests_macos to be unflaky (flutter/flutter#142598) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_x64_ios hot_mode_dev_cycle_ios__benchmark to be unflaky (flutter/flutter#142597) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_arm64 native_ui_tests_macos to be unflaky (flutter/flutter#142599) 2024-01-31 fluttergithubbot@gmail.com Marks Windows_android hot_mode_dev_cycle_win__benchmark to be flaky (flutter/flutter#142609) 2024-01-31 fluttergithubbot@gmail.com Marks Mac_arm64_ios integration_test_test_ios to be unflaky (flutter/flutter#142596) 2024-01-31 polinach@google.com Mark test that leaks image. (flutter/flutter#142539) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from b9bc256156b8 to e0d8f472a1b6 (1 revision) (flutter/flutter#142623) 2024-01-31 31859944+LongCatIsLooong@users.noreply.github.com Fix unresponsive mouse tooltip (flutter/flutter#142282) 2024-01-31 fluttergithubbot@gmail.com Marks Linux_android_emu android_defines_test to be unflaky (flutter/flutter#142591) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from 447dd212447e to b9bc256156b8 (6 revisions) (flutter/flutter#142617) 2024-01-31 engine-flutter-autoroll@skia.org Roll Packages from 25abb5d to 5b48c44 (4 revisions) (flutter/flutter#142616) 2024-01-31 64037520+SelaseKay@users.noreply.github.com Fix null operator error when tapping on 'MenuItemButton' (flutter/flutter#142230) 2024-01-31 engine-flutter-autoroll@skia.org Roll Flutter Engine from 8e7df85f7d11 to 447dd212447e (2 revisions) (flutter/flutter#142587) 2024-01-31 katelovett@google.com Split out AppBar/SliverAppBar material tests (flutter/flutter#142560) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. ...
…ndBuilder" (#142748) Reverts #141818 Initiated by: XilaiZhang This change reverts the following previous change: Original Description: Fixes #139456, #130335, #89563. Two new properties have been added to ButtonStyle to make it possible to insert arbitrary state-dependent widgets in a button's background or foreground. These properties can be specified for an individual button, using the style parameter, or for all buttons using a button theme's style parameter. The new ButtonStyle properties are `backgroundBuilder` and `foregroundBuilder` and their (function) types are: ```dart typedef ButtonLayerBuilder = Widget Function( BuildContext context, Set<MaterialState> states, Widget? child ); ``` The new builder functions are called whenever the button is built and the `states` parameter communicates the pressed/hovered/etc state fo the button. ## `backgroundBuilder` 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. By default the returned widget is clipped to the Material's ButtonStyle.shape. The `backgroundBuilder` can be used to add a gradient to the button's background. Here's an example that creates a yellow/orange gradient background: ![opaque-gradient-bg](https://github.com/flutter/flutter/assets/1377460/80df8368-e7cf-49ef-aee7-2776a573644c) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` Because the background widget becomes the child of the button's Material, if it's opaque (as it is in this case) then it obscures the overlay highlights which are painted on the button's Material. To ensure that the highlights show through one can decorate the background with an `Ink` widget. This version also overrides the overlay color to be (shades of) red, because that makes the highlights look a little nicer with the yellow/orange background. ![ink-gradient-bg](https://github.com/flutter/flutter/assets/1377460/68a49733-f30e-44a1-a948-dc8cc95e1716) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( overlayColor: Colors.red, backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { return Ink( decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` Now the button's overlay highlights are painted on the Ink widget. An Ink widget isn't needed if the background is sufficiently translucent. This version of the example creates a translucent backround widget. ![translucent-graident-bg](https://github.com/flutter/flutter/assets/1377460/3b016e1f-200a-4d07-8111-e20d29f18014) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( overlayColor: Colors.red, backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: [ Colors.orange.withOpacity(0.5), Colors.yellow.withOpacity(0.5), ]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` One can also decorate the background with an image. In this example, the button's background is an burlap texture image. The foreground color has been changed to black to make the button's text a little clearer relative to the mottled brown backround. ![burlap-bg](https://github.com/flutter/flutter/assets/1377460/f2f61ab1-10d9-43a4-bd63-beecdce33b45) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: Colors.black, backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { return Ink( decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(burlapUrl), fit: BoxFit.cover, ), ), child: child, ); }, ), child: Text('Text Button'), ) ``` The background widget can depend on the `states` parameter. In this example the blue/orange gradient flips horizontally when the button is hovered/pressed. ![gradient-flip](https://github.com/flutter/flutter/assets/1377460/c6c6fe26-ae47-445b-b82d-4605d9583bd8) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { final Color color1 = Colors.blue.withOpacity(0.5); final Color color2 = Colors.orange.withOpacity(0.5); return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: switch (states.contains(MaterialState.hovered)) { true => <Color>[color1, color2], false => <Color>[color2, color1], }, ), ), child: child, ); }, ), child: Text('Text Button'), ) ``` The preceeding examples have not included a BoxDecoration border because ButtonStyle already supports `ButtonStyle.shape` and `ButtonStyle.side` parameters that can be uesd to define state-dependent borders. Borders defined with the ButtonStyle side parameter match the button's shape. To add a border that changes color when the button is hovered or pressed, one must specify the side property using `copyWith`, since there's no `styleFrom` shorthand for this case. ![border-gradient-bg](https://github.com/flutter/flutter/assets/1377460/63cffcd3-0dcf-4eb1-aed5-d14adf1e57f6) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: Colors.indigo, backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { final Color color1 = Colors.blue.withOpacity(0.5); final Color color2 = Colors.orange.withOpacity(0.5); return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: switch (states.contains(MaterialState.hovered)) { true => <Color>[color1, color2], false => <Color>[color2, color1], }, ), ), child: child, ); }, ).copyWith( side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> states) { if (states.contains(MaterialState.hovered)) { return BorderSide(width: 3, color: Colors.yellow); } return null; // defer to the default }), ), child: Text('Text Button'), ) ``` Although all of the examples have created a ButtonStyle locally and only applied it to one button, they could have configured the `ThemeData.textButtonTheme` instead and applied the style to all TextButtons. And, of course, all of this works for all of the ButtonStyleButton classes, not just TextButton. ## `foregroundBuilder` Creates a Widget that contains the button's child parameter. 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]. The `foregroundBuilder` can be used to wrap the button's child, e.g. with a border or a `ShaderMask` or as a state-dependent substitute for the child. This example adds a border that's just applied to the child. The border only appears when the button is hovered/pressed. ![border-fg](https://github.com/flutter/flutter/assets/1377460/687a3245-fe68-4983-a04e-5fcc77f8aa21) ```dart ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return DecoratedBox( decoration: BoxDecoration( border: states.contains(MaterialState.hovered) ? Border(bottom: BorderSide(color: colorScheme.primary)) : Border(), // essentially "no border" ), child: child, ); }, ), child: Text('Text Button'), ) ``` The foregroundBuilder can be used with `ShaderMask` to change the way the button's child is rendered. In this example the ShaderMask's gradient causes the button's child to fade out on top. ![shader_mask_fg](https://github.com/flutter/flutter/assets/1377460/54010f24-e65d-4551-ae58-712135df3d8d) ```dart ElevatedButton( onPressed: () { }, style: ElevatedButton.styleFrom( foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return ShaderMask( shaderCallback: (Rect bounds) { return LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: <Color>[ colorScheme.primary, colorScheme.primaryContainer, ], ).createShader(bounds); }, blendMode: BlendMode.srcATop, child: child, ); }, ), child: const Text('Elevated Button'), ) ``` A commonly requested configuration for butttons has the developer provide images, one for pressed/hovered/normal state. You can use the foregroundBuilder to create a button that fades between a normal image and another image when the button is pressed. In this case the foregroundBuilder doesn't use the child it's passed, even though we've provided the required TextButton child parameter. ![image-button](https://github.com/flutter/flutter/assets/1377460/f5b1a22f-43ce-4be3-8e70-06de4c958380) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) { final String url = states.contains(MaterialState.pressed) ? smiley2Url : smiley1Url; return AnimatedContainer( width: 100, height: 100, duration: Duration(milliseconds: 300), decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(url), fit: BoxFit.contain, ), ), ); }, ), child: Text('No Child'), ) ``` In this example the button's default overlay appears when the button is hovered and pressed. Another image can be used to indicate the hovered state and the default overlay can be defeated by specifying `Colors.transparent` for the `overlayColor`: ![image-per-state](https://github.com/flutter/flutter/assets/1377460/7ab9da2f-f661-4374-b395-c2e0c7c4cf13) ```dart 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: 100, height: 100, duration: Duration(milliseconds: 300), decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(url), fit: BoxFit.contain, ), ), ); }, ), child: Text('No Child'), ) ```
…dBuilder (#142762) Reland #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.
Fixes #139456, #130335, #89563.
Two new properties have been added to ButtonStyle to make it possible to insert arbitrary state-dependent widgets in a button's background or foreground. These properties can be specified for an individual button, using the style parameter, or for all buttons using a button theme's style parameter.
The new ButtonStyle properties are
backgroundBuilder
andforegroundBuilder
and their (function) types are:The new builder functions are called whenever the button is built and the
states
parameter communicates the pressed/hovered/etc state fo the button.backgroundBuilder
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. By default the returned widget is clipped to the Material's ButtonStyle.shape.The
backgroundBuilder
can be used to add a gradient to the button's background. Here's an example that creates a yellow/orange gradient background:Because the background widget becomes the child of the button's Material, if it's opaque (as it is in this case) then it obscures the overlay highlights which are painted on the button's Material. To ensure that the highlights show through one can decorate the background with an
Ink
widget. This version also overrides the overlay color to be (shades of) red, because that makes the highlights look a little nicer with the yellow/orange background.Now the button's overlay highlights are painted on the Ink widget. An Ink widget isn't needed if the background is sufficiently translucent. This version of the example creates a translucent backround widget.
One can also decorate the background with an image. In this example, the button's background is an burlap texture image. The foreground color has been changed to black to make the button's text a little clearer relative to the mottled brown backround.
The background widget can depend on the
states
parameter. In this example the blue/orange gradient flips horizontally when the button is hovered/pressed.The preceeding examples have not included a BoxDecoration border because ButtonStyle already supports
ButtonStyle.shape
andButtonStyle.side
parameters that can be uesd to define state-dependent borders. Borders defined with the ButtonStyle side parameter match the button's shape. To add a border that changes color when the button is hovered or pressed, one must specify the side property usingcopyWith
, since there's nostyleFrom
shorthand for this case.Although all of the examples have created a ButtonStyle locally and only applied it to one button, they could have configured the
ThemeData.textButtonTheme
instead and applied the style to all TextButtons. And, of course, all of this works for all of the ButtonStyleButton classes, not just TextButton.foregroundBuilder
Creates a Widget that contains the button's child parameter. 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].
The
foregroundBuilder
can be used to wrap the button's child, e.g. with a border or aShaderMask
or as a state-dependent substitute for the child.This example adds a border that's just applied to the child. The border only appears when the button is hovered/pressed.
The foregroundBuilder can be used with
ShaderMask
to change the way the button's child is rendered. In this example the ShaderMask's gradient causes the button's child to fade out on top.A commonly requested configuration for butttons has the developer provide images, one for pressed/hovered/normal state. You can use the foregroundBuilder to create a button that fades between a normal image and another image when the button is pressed. In this case the foregroundBuilder doesn't use the child it's passed, even though we've provided the required TextButton child parameter.
In this example the button's default overlay appears when the button is hovered and pressed. Another image can be used to indicate the hovered state and the default overlay can be defeated by specifying
Colors.transparent
for theoverlayColor
: