flutter/examples/api/lib/material
Hans Muller ff6c8f5d37
Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#141818)
Fixes https://github.com/flutter/flutter/issues/139456, https://github.com/flutter/flutter/issues/130335, https://github.com/flutter/flutter/issues/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'),
)
```
2024-02-01 00:02:23 +00:00
..
about Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
action_buttons Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
action_chip Enable dangling_library_doc_comments and library_annotations lints (#117365) 2022-12-20 16:03:21 -08:00
animated_icon Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
app [Reland] Introduce AnimationStyle (#138721) 2023-11-20 15:24:41 -08:00
app_bar Replaces textScaleFactor with TextScaler (#128522) 2023-07-17 17:56:07 +00:00
autocomplete Fixed a lot of typos (#141431) 2024-01-12 22:10:25 +00:00
banner Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
bottom_app_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
bottom_navigation_bar Enable strict-inference (#135043) 2023-09-20 19:59:08 +00:00
bottom_sheet Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
button_style Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
card Add Card.filled and Card.outlined factory methods (#136229) 2023-11-01 23:29:49 +00:00
checkbox Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
checkbox_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
choice_chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
color_scheme Implement switch expressions in examples/ and animation/ (#139882) 2023-12-11 22:56:04 +00:00
context_menu Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
data_table Fix DataTable example not being scrollable (#131556) 2023-09-11 18:55:53 -05:00
date_picker Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
dialog Adaptive alert dialog (#124336) 2023-04-18 23:00:03 +00:00
divider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
drawer Add missing example links (#130521) 2023-07-17 18:24:49 +00:00
dropdown Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
dropdown_menu Improve DropdownMenu sample code for requestFocusOnTap on mobile platforms (#134867) 2023-09-18 08:33:14 +00:00
elevated_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
expansion_panel Update ExpansionPanel example for the updated expansionCallback callback (#132837) 2023-08-21 20:13:22 +00:00
expansion_tile Add AnimationStyle to ExpansionTile (#139664) 2023-12-06 16:40:24 +00:00
filled_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
filter_chip Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
flexible_space_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
floating_action_button Add FAB Additional Color Mappings example (#133453) 2023-08-29 17:31:02 +00:00
floating_action_button_location Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
icon_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
ink Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
ink_well Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
input_chip Add an example for InputChip generated by user input (#130645) 2023-09-01 00:02:04 +00:00
input_decorator Updated InputDecoratorExamples for M3 (#128065) 2023-06-01 15:22:03 -07:00
list_tile Fix typos in ListTile examples. (#129606) 2023-06-29 06:29:03 +00:00
material_state Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
menu_anchor Convert menus to use OverlayPortal (#130534) 2023-10-18 20:13:08 +00:00
navigation_bar Remove unused generic type from BottomSheet (#137791) 2023-11-03 22:25:37 +00:00
navigation_drawer Add missing example links (#130521) 2023-07-17 18:24:49 +00:00
navigation_rail Add Badge widget to NavigationBar and NavigationRail examples (#129834) 2023-07-11 09:30:05 +00:00
outlined_button Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
page_transitions_theme Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
paginated_data_table PaginatedDataTable improvements (#131374) 2023-08-15 00:55:07 +00:00
platform_menu_bar Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
popup_menu [Reland] Introduce AnimationStyle (#138721) 2023-11-20 15:24:41 -08:00
progress_indicator Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
radio Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
radio_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
range_slider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
refresh_indicator Refactor refresh_indicator.1.dart to not use shrinkwrap (#129377) 2023-07-11 20:04:17 +00:00
reorderable_list Add a ReorderableListView example with cards + cleanup existing tests (#126155) 2023-05-05 16:39:11 +00:00
scaffold Remove unused generic type from BottomSheet (#137791) 2023-11-03 22:25:37 +00:00
scrollbar fix a Scrollbar example crash (#127925) 2023-09-08 09:40:49 +00:00
search_anchor Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
segmented_button Add SegmentedButton.styleFrom (#137542) 2024-01-03 21:26:02 +00:00
selectable_region Fix SelectionArea select-word edge cases (#136920) 2023-12-11 21:32:55 +00:00
selection_area Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
selection_container Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
slider Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
snack_bar Add missing links to examples that aren't linked anywhere (#130422) 2023-07-12 20:08:05 +00:00
stepper Adds support for StepStyle visual property bundle to the Step widget (#140825) 2024-01-12 16:35:08 +00:00
switch Adaptive Switch (#130425) 2023-11-07 10:26:23 -08:00
switch_list_tile Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
tab_controller Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
tabs Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
text_button Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#141818) 2024-02-01 00:02:23 +00:00
text_field Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
text_form_field Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
theme Rename Sample classes (#124080) 2023-04-04 20:34:29 +00:00
theme_data Fix Typos (#137292) 2023-10-26 23:55:38 +00:00
time_picker Remove textScaleFactor references from flutter/flutter (#142271) 2024-01-26 19:12:24 +00:00
toggle_buttons Updated TabBar and ToggleButtons examples (#128088) 2023-06-02 01:05:31 +00:00
tooltip Tooltip docs: Recommend setting preferBelow to false in theme (#135879) 2023-11-03 20:18:36 +00:00