Replace ButtonBar.bar method with ButtonBarTheme (#37544)

* Added new ButtonBarTheme to replace the deprecated ButtonTheme.bar method.

* Responding to PR feedback.

* [Material] Create material Banner component (#36880)

This PR creates a new material widget for the Banner component. This includes a theme as well. This widget can be dropped into any application, ideally at the top of a listview or scrollview.

(cherry picked from commit 35b6d668e197035ea2b57adb99041542982e8be0)

Removed the use of ButtonTheme.bar in the Banner implementation.

* Updated documentation from PR review comments.
diff --git a/examples/flutter_gallery/lib/demo/material/cards_demo.dart b/examples/flutter_gallery/lib/demo/material/cards_demo.dart
index 4e3b788..39c1106 100644
--- a/examples/flutter_gallery/lib/demo/material/cards_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/cards_demo.dart
@@ -324,22 +324,20 @@
     if (destination.type == CardDemoType.standard) {
       children.add(
         // share, explore buttons
-        ButtonTheme.bar(
-          child: ButtonBar(
-            alignment: MainAxisAlignment.start,
-            children: <Widget>[
-              FlatButton(
-                child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'),
-                textColor: Colors.amber.shade500,
-                onPressed: () { print('pressed'); },
-              ),
-              FlatButton(
-                child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'),
-                textColor: Colors.amber.shade500,
-                onPressed: () { print('pressed'); },
-              ),
-            ],
-          ),
+        ButtonBar(
+          alignment: MainAxisAlignment.start,
+          children: <Widget>[
+            FlatButton(
+              child: Text('SHARE', semanticsLabel: 'Share ${destination.title}'),
+              textColor: Colors.amber.shade500,
+              onPressed: () { print('pressed'); },
+            ),
+            FlatButton(
+              child: Text('EXPLORE', semanticsLabel: 'Explore ${destination.title}'),
+              textColor: Colors.amber.shade500,
+              onPressed: () { print('pressed'); },
+            ),
+          ],
         ),
       );
     }
diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart
index e951962..076af21 100644
--- a/packages/flutter/lib/material.dart
+++ b/packages/flutter/lib/material.dart
@@ -32,6 +32,7 @@
 export 'src/material/bottom_sheet_theme.dart';
 export 'src/material/button.dart';
 export 'src/material/button_bar.dart';
+export 'src/material/button_bar_theme.dart';
 export 'src/material/button_theme.dart';
 export 'src/material/card.dart';
 export 'src/material/card_theme.dart';
diff --git a/packages/flutter/lib/src/material/banner.dart b/packages/flutter/lib/src/material/banner.dart
index bac6fd9..b3364c9 100644
--- a/packages/flutter/lib/src/material/banner.dart
+++ b/packages/flutter/lib/src/material/banner.dart
@@ -118,11 +118,9 @@
         ?? bannerTheme.padding
         ?? const EdgeInsetsDirectional.only(end: 16.0);
 
-    final Widget buttonBar = ButtonTheme.bar(
+    final Widget buttonBar = ButtonBar(
       layoutBehavior: ButtonBarLayoutBehavior.constrained,
-      child: ButtonBar(
-        children: actions,
-      ),
+      children: actions,
     );
 
     final Color backgroundColor = this.backgroundColor
diff --git a/packages/flutter/lib/src/material/button_bar.dart b/packages/flutter/lib/src/material/button_bar.dart
index 2e4ed41..4e5298c 100644
--- a/packages/flutter/lib/src/material/button_bar.dart
+++ b/packages/flutter/lib/src/material/button_bar.dart
@@ -4,6 +4,7 @@
 
 import 'package:flutter/widgets.dart';
 
+import 'button_bar_theme.dart';
 import 'button_theme.dart';
 import 'dialog.dart';
 import 'flat_button.dart';
@@ -11,12 +12,24 @@
 
 /// An end-aligned row of buttons.
 ///
-/// Places the buttons horizontally according to the padding in the current
-/// [ButtonTheme]. The children are laid out in a [Row] with
-/// [MainAxisAlignment.end]. When the [Directionality] is [TextDirection.ltr],
-/// the button bar's children are right justified and the last child becomes
-/// the rightmost child. When the [Directionality] [TextDirection.rtl] the
-/// children are left justified and the last child becomes the leftmost child.
+/// Places the buttons horizontally according to the [buttonPadding]. The
+/// children are laid out in a [Row] with [MainAxisAlignment.end]. When the
+/// [Directionality] is [TextDirection.ltr], the button bar's children are
+/// right justified and the last child becomes the rightmost child. When the
+/// [Directionality] [TextDirection.rtl] the children are left justified and
+/// the last child becomes the leftmost child.
+///
+/// The [ButtonBar] can be configured with a [ButtonBarTheme]. For any null
+/// property on the ButtonBar, the surrounding ButtonBarTheme's property
+/// will be used instead. If the ButtonBarTheme's property is null
+/// as well, the property will default to a value described in the field
+/// documentation below.
+///
+/// The [children] are wrapped in a [ButtonTheme] that is a copy of the
+/// surrounding ButtonTheme with the button properties overridden by the
+/// properties of the ButtonBar as described above. These properties include
+/// [buttonTextTheme], [buttonMinWidth], [buttonHeight], [buttonPadding],
+/// and [buttonAlignedDropdown].
 ///
 /// Used by [Dialog] to arrange the actions at the bottom of the dialog.
 ///
@@ -26,24 +39,84 @@
 ///  * [FlatButton], another kind of button.
 ///  * [Card], at the bottom of which it is common to place a [ButtonBar].
 ///  * [Dialog], which uses a [ButtonBar] for its actions.
-///  * [ButtonTheme], which configures the [ButtonBar].
+///  * [ButtonBarTheme], which configures the [ButtonBar].
 class ButtonBar extends StatelessWidget {
   /// Creates a button bar.
   ///
-  /// The alignment argument defaults to [MainAxisAlignment.end].
+  /// Both [buttonMinWidth] and [buttonHeight] must be non-negative if they
+  /// are not null.
   const ButtonBar({
     Key key,
-    this.alignment = MainAxisAlignment.end,
-    this.mainAxisSize = MainAxisSize.max,
+    this.alignment,
+    this.mainAxisSize,
+    this.buttonTextTheme,
+    this.buttonMinWidth,
+    this.buttonHeight,
+    this.buttonPadding,
+    this.buttonAlignedDropdown,
+    this.layoutBehavior,
     this.children = const <Widget>[],
-  }) : super(key: key);
+  }) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
+       assert(buttonHeight == null || buttonHeight >= 0.0),
+       super(key: key);
 
   /// How the children should be placed along the horizontal axis.
+  ///
+  /// If null then it will use [ButtonBarTheme.alignment]. If that is null,
+  /// it will default to [MainAxisAlignment.end].
   final MainAxisAlignment alignment;
 
   /// How much horizontal space is available. See [Row.mainAxisSize].
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.mainAxisSize].
+  /// If that is null, it will default to [MainAxisSize.max].
   final MainAxisSize mainAxisSize;
 
+  /// Overrides the surrounding [ButtonTheme.textTheme] to define a button's
+  /// base colors, size, internal padding and shape.
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.buttonTextTheme].
+  /// If that is null, it will default to [ButtonTextTheme.primary].
+  final ButtonTextTheme buttonTextTheme;
+
+  /// Overrides the surrounding [ButtonThemeData.minWidth] to define a button's
+  /// minimum width.
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.buttonMinWidth].
+  /// If that is null, it will default to 64.0 logical pixels.
+  final double buttonMinWidth;
+
+  /// Overrides the surrounding [ButtonThemeData.height] to define a button's
+  /// minimum height.
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.buttonHeight].
+  /// If that is null, it will default to 36.0 logical pixels.
+  final double buttonHeight;
+
+  /// Overrides the surrounding [ButtonThemeData.padding] to define the padding
+  /// for a button's child (typically the button's label).
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.buttonPadding].
+  /// If that is null, it will default to 8.0 logical pixels on the left
+  /// and right.
+  final EdgeInsetsGeometry buttonPadding;
+
+  /// Overrides the surrounding [ButtonThemeData.alignedDropdown] to define whether
+  /// a [DropdownButton] menu's width will match the button's width.
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.buttonAlignedDropdown].
+  /// If that is null, it will default to false.
+  final bool buttonAlignedDropdown;
+
+  /// Defines whether a [ButtonBar] should size itself with a minimum size
+  /// constraint or with padding.
+  ///
+  /// Overrides the surrounding [ButtonThemeData.layoutBehavior].
+  ///
+  /// If null then it will use the surrounding [ButtonBarTheme.layoutBehavior].
+  /// If that is null, it will default [ButtonBarLayoutBehavior.padded].
+  final ButtonBarLayoutBehavior layoutBehavior;
+
   /// The buttons to arrange horizontally.
   ///
   /// Typically [RaisedButton] or [FlatButton] widgets.
@@ -51,18 +124,32 @@
 
   @override
   Widget build(BuildContext context) {
-    final ButtonThemeData buttonTheme = ButtonTheme.of(context);
+    final ButtonThemeData parentButtonTheme = ButtonTheme.of(context);
+    final ButtonBarThemeData barTheme = ButtonBarTheme.of(context);
+
+    final ButtonThemeData buttonTheme = parentButtonTheme.copyWith(
+      textTheme: buttonTextTheme ?? barTheme.buttonTextTheme ?? ButtonTextTheme.primary,
+      minWidth: buttonMinWidth ?? barTheme.buttonMinWidth ?? 64.0,
+      height: buttonHeight ?? barTheme.buttonHeight ?? 36.0,
+      padding: buttonPadding ?? barTheme.buttonPadding ?? const EdgeInsets.symmetric(horizontal: 8.0),
+      alignedDropdown: buttonAlignedDropdown ?? barTheme.buttonAlignedDropdown ?? false,
+      layoutBehavior: layoutBehavior ?? barTheme.layoutBehavior ?? ButtonBarLayoutBehavior.padded,
+    );
+
     // We divide by 4.0 because we want half of the average of the left and right padding.
     final double paddingUnit = buttonTheme.padding.horizontal / 4.0;
-    final Widget child = Row(
-      mainAxisAlignment: alignment,
-      mainAxisSize: mainAxisSize,
-      children: children.map<Widget>((Widget child) {
-        return Padding(
-          padding: EdgeInsets.symmetric(horizontal: paddingUnit),
-          child: child,
-        );
-      }).toList(),
+    final Widget child = ButtonTheme.fromButtonThemeData(
+      data: buttonTheme,
+      child: Row(
+        mainAxisAlignment: alignment ?? barTheme.alignment ?? MainAxisAlignment.end,
+        mainAxisSize: mainAxisSize ?? barTheme.mainAxisSize ?? MainAxisSize.max,
+        children: children.map<Widget>((Widget child) {
+          return Padding(
+            padding: EdgeInsets.symmetric(horizontal: paddingUnit),
+            child: child,
+          );
+        }).toList(),
+      ),
     );
     switch (buttonTheme.layoutBehavior) {
       case ButtonBarLayoutBehavior.padded:
diff --git a/packages/flutter/lib/src/material/button_bar_theme.dart b/packages/flutter/lib/src/material/button_bar_theme.dart
new file mode 100644
index 0000000..b2bc04a
--- /dev/null
+++ b/packages/flutter/lib/src/material/button_bar_theme.dart
@@ -0,0 +1,240 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui' show lerpDouble;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+import 'button_theme.dart';
+import 'theme.dart';
+
+/// Defines the visual properties of [ButtonBar] widgets.
+///
+/// Used by [ButtonBarTheme] to control the visual properties of [ButtonBar]
+/// instances in a widget subtree.
+///
+/// To obtain this configuration, use [ButtonBarTheme.of] to access the closest
+/// ancestor [ButtonBarTheme] of the current [BuildContext].
+///
+/// See also:
+///
+///   * [ButtonBarTheme], an [InheritedWidget] that propagates the theme down
+///     its subtree.
+///   * [ButtonBar], which uses this to configure itself and its children
+///     button widgets.
+class ButtonBarThemeData extends Diagnosticable {
+  /// Constructs the set of properties used to configure [ButtonBar] widgets.
+  ///
+  /// Both [buttonMinWidth] and [buttonHeight] must be non-negative if they
+  /// are not null.
+  const ButtonBarThemeData({
+    this.alignment,
+    this.mainAxisSize,
+    this.buttonTextTheme,
+    this.buttonMinWidth,
+    this.buttonHeight,
+    this.buttonPadding,
+    this.buttonAlignedDropdown,
+    this.layoutBehavior,
+  }) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
+       assert(buttonHeight == null || buttonHeight >= 0.0);
+
+  /// How the children should be placed along the horizontal axis.
+  final MainAxisAlignment alignment;
+
+  /// How much horizontal space is available. See [Row.mainAxisSize].
+  final MainAxisSize mainAxisSize;
+
+  /// Defines a [ButtonBar] button's base colors, and the defaults for
+  /// the button's minimum size, internal padding, and shape.
+  ///
+  /// This will override the surrounding [ButtonTheme.textTheme] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// Despite the name, this property is not a [TextTheme], its value is not a
+  /// collection of [TextStyle]s.
+  final ButtonTextTheme buttonTextTheme;
+
+  /// The minimum width for [ButtonBar] buttons.
+  ///
+  /// This will override the surrounding [ButtonTheme.minWidth] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// The actual horizontal space allocated for a button's child is
+  /// at least this value less the theme's horizontal [padding].
+  final double buttonMinWidth;
+
+  /// The minimum height for [ButtonBar] buttons.
+  ///
+  /// This will override the surrounding [ButtonTheme.height] setting
+  /// for buttons contained in the [ButtonBar].
+  final double buttonHeight;
+
+  /// Padding for a [ButtonBar] button's child (typically the button's label).
+  ///
+  /// This will override the surrounding [ButtonTheme.padding] setting
+  /// for buttons contained in the [ButtonBar].
+  final EdgeInsetsGeometry buttonPadding;
+
+  /// If true, then a [DropdownButton] menu's width will match the [ButtonBar]
+  /// button's width.
+  ///
+  /// If false, then the dropdown's menu will be wider than
+  /// its button. In either case the dropdown button will line up the leading
+  /// edge of the menu's value with the leading edge of the values
+  /// displayed by the menu items.
+  ///
+  /// This will override the surrounding [ButtonTheme.alignedDropdown] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// This property only affects [DropdownButton] contained in a [ButtonBar]
+  /// and its menu.
+  final bool buttonAlignedDropdown;
+
+  /// Defines whether a [ButtonBar] should size itself with a minimum size
+  /// constraint or with padding.
+  final ButtonBarLayoutBehavior layoutBehavior;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  ButtonBarThemeData copyWith({
+    MainAxisAlignment alignment,
+    MainAxisSize mainAxisSize,
+    ButtonTextTheme buttonTextTheme,
+    double buttonMinWidth,
+    double buttonHeight,
+    EdgeInsetsGeometry buttonPadding,
+    bool buttonAlignedDropdown,
+    ButtonBarLayoutBehavior layoutBehavior,
+  }) {
+    return ButtonBarThemeData(
+      alignment: alignment ?? this.alignment,
+      mainAxisSize: mainAxisSize ?? this.mainAxisSize,
+      buttonTextTheme: buttonTextTheme ?? this.buttonTextTheme,
+      buttonMinWidth: buttonMinWidth ?? this.buttonMinWidth,
+      buttonHeight: buttonHeight ?? this.buttonHeight,
+      buttonPadding: buttonPadding ?? this.buttonPadding,
+      buttonAlignedDropdown: buttonAlignedDropdown ?? this.buttonAlignedDropdown,
+      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
+    );
+  }
+
+  /// Linearly interpolate between two button bar themes.
+  ///
+  /// If both arguments are null, then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ButtonBarThemeData lerp(ButtonBarThemeData a, ButtonBarThemeData b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return ButtonBarThemeData(
+      alignment: t < 0.5 ? a.alignment : b.alignment,
+      mainAxisSize: t < 0.5 ? a.mainAxisSize : b.mainAxisSize,
+      buttonTextTheme: t < 0.5 ? a.buttonTextTheme : b.buttonTextTheme,
+      buttonMinWidth: lerpDouble(a?.buttonMinWidth, b?.buttonMinWidth, t),
+      buttonHeight: lerpDouble(a?.buttonHeight, b?.buttonHeight, t),
+      buttonPadding: EdgeInsets.lerp(a?.buttonPadding, b?.buttonPadding, t),
+      buttonAlignedDropdown: t < 0.5 ? a.buttonAlignedDropdown : b.buttonAlignedDropdown,
+      layoutBehavior: t < 0.5 ? a.layoutBehavior : b.layoutBehavior,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      alignment,
+      mainAxisSize,
+      buttonTextTheme,
+      buttonMinWidth,
+      buttonHeight,
+      buttonPadding,
+      buttonAlignedDropdown,
+      layoutBehavior,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    final ButtonBarThemeData typedOther = other;
+    return typedOther.alignment == alignment
+        && typedOther.mainAxisSize == mainAxisSize
+        && typedOther.buttonTextTheme == buttonTextTheme
+        && typedOther.buttonMinWidth == buttonMinWidth
+        && typedOther.buttonHeight == buttonHeight
+        && typedOther.buttonPadding == buttonPadding
+        && typedOther.buttonAlignedDropdown == buttonAlignedDropdown
+        && typedOther.layoutBehavior == layoutBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MainAxisAlignment>('alignment', alignment, defaultValue: null));
+    properties.add(DiagnosticsProperty<MainAxisSize>('mainAxisSize', mainAxisSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonTextTheme>('textTheme', buttonTextTheme, defaultValue: null));
+    properties.add(DoubleProperty('minWidth', buttonMinWidth, defaultValue: null));
+    properties.add(DoubleProperty('height', buttonHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', buttonPadding, defaultValue: null));
+    properties.add(FlagProperty(
+        'buttonAlignedDropdown',
+        value: buttonAlignedDropdown,
+        ifTrue: 'dropdown width matches button',
+        defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonBarLayoutBehavior>('layoutBehavior', layoutBehavior, defaultValue: null));
+  }
+}
+
+/// Applies a button bar theme to descendant [ButtonBar] widgets.
+///
+/// A button bar theme describes the layout and properties for the buttons
+/// contained in a [ButtonBar].
+///
+/// Descendant widgets obtain the current theme's [ButtonBarTheme] object using
+/// [ButtonBarTheme.of]. When a widget uses [ButtonBarTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// A button bar theme can be specified as part of the overall Material theme
+/// using [ThemeData.buttonBarTheme].
+///
+/// See also:
+///
+///  * [ButtonBarThemeData], which describes the actual configuration of a button
+///    bar theme.
+class ButtonBarTheme extends InheritedWidget {
+  /// Constructs a button bar theme that configures all descendent [ButtonBar]
+  /// widgets.
+  ///
+  /// The [data] must not be null.
+  const ButtonBarTheme({
+    Key key,
+    @required this.data,
+    Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties used for all descendant [ButtonBar] widgets.
+  final ButtonBarThemeData data;
+
+  /// Returns the configuration [data] from the closest [ButtonBarTheme]
+  /// ancestor. If there is no ancestor, it returns [ThemeData.buttonBarTheme].
+  /// Applications can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ButtonBarThemeData theme = ButtonBarTheme.of(context);
+  /// ```
+  static ButtonBarThemeData of(BuildContext context) {
+    final ButtonBarTheme buttonBarTheme = context.inheritFromWidgetOfExactType(ButtonBarTheme);
+    return buttonBarTheme?.data ?? Theme.of(context).buttonBarTheme;
+  }
+
+  @override
+  bool updateShouldNotify(ButtonBarTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/packages/flutter/lib/src/material/button_theme.dart b/packages/flutter/lib/src/material/button_theme.dart
index 2bd426f..67afdc4 100644
--- a/packages/flutter/lib/src/material/button_theme.dart
+++ b/packages/flutter/lib/src/material/button_theme.dart
@@ -120,20 +120,51 @@
   }) : assert(data != null),
        super(key: key, child: child);
 
+  // TODO(darrenaustin): remove after this deprecation warning has been on
+  // stable for a couple of releases.
+  // See https://github.com/flutter/flutter/issues/37333
+  //
   /// Creates a button theme that is appropriate for button bars, as used in
   /// dialog footers and in the headers of data tables.
   ///
-  /// This theme is denser, with a smaller [minWidth] and [padding], than the
-  /// default theme. Also, this theme uses [ButtonTextTheme.accent] rather than
-  /// [ButtonTextTheme.normal].
+  /// Deprecated. Please use [ButtonBarTheme] instead which offers more
+  /// flexibility to configure [ButtonBar] widgets.
   ///
-  /// For best effect, the label of the button at the edge of the container
-  /// should have text that ends up wider than 64.0 pixels. This ensures that
-  /// the alignment of the text matches the alignment of the edge of the
-  /// container.
+  /// To migrate instances of code that were just wrapping a [ButtonBar]:
   ///
-  /// For example, buttons at the bottom of [Dialog] or [Card] widgets use this
-  /// button theme.
+  /// ```dart
+  /// ButtonTheme.bar(
+  ///   child: ButtonBar(...)
+  /// );
+  /// ```
+  ///
+  /// you can just remove the `ButtonTheme.bar` as the defaults are now handled
+  /// by [ButtonBar] directly.
+  ///
+  /// If you have more complicated usages of `ButtonTheme.bar` like:
+  ///
+  /// ```dart
+  /// ButtonTheme.bar(
+  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
+  ///   textTheme: ButtonTextTheme.accent,
+  ///   child: ButtonBar(...),
+  /// );
+  /// ```
+  ///
+  /// you can remove the `ButtonTheme.bar` and move the parameters to the
+  /// [ButtonBar] instance directly:
+  ///
+  /// ```dart
+  /// ButtonBar(
+  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
+  ///   textTheme: ButtonTextTheme.accent,
+  ///   ...
+  /// );
+  /// ```
+  ///
+  /// You can also replace the defaults for all [ButtonBar] widgets by updating
+  /// [ThemeData.buttonBarTheme] for your app.
+  @Deprecated('use ButtonBarTheme instead')
   ButtonTheme.bar({
     Key key,
     ButtonTextTheme textTheme = ButtonTextTheme.accent,
diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart
index a8c45b6..1ff6aa0 100644
--- a/packages/flutter/lib/src/material/card.dart
+++ b/packages/flutter/lib/src/material/card.dart
@@ -36,19 +36,17 @@
 ///             title: Text('The Enchanted Nightingale'),
 ///             subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
 ///           ),
-///           ButtonTheme.bar( // make buttons use the appropriate styles for cards
-///             child: ButtonBar(
-///               children: <Widget>[
-///                 FlatButton(
-///                   child: const Text('BUY TICKETS'),
-///                   onPressed: () { /* ... */ },
-///                 ),
-///                 FlatButton(
-///                   child: const Text('LISTEN'),
-///                   onPressed: () { /* ... */ },
-///                 ),
-///               ],
-///             ),
+///           ButtonBar(
+///             children: <Widget>[
+///               FlatButton(
+///                 child: const Text('BUY TICKETS'),
+///                 onPressed: () { /* ... */ },
+///               ),
+///               FlatButton(
+///                 child: const Text('LISTEN'),
+///                 onPressed: () { /* ... */ },
+///               ),
+///             ],
 ///           ),
 ///         ],
 ///       ),
@@ -92,8 +90,7 @@
 /// See also:
 ///
 ///  * [ListTile], to display icons and text in a card.
-///  * [ButtonBar], to display buttons at the bottom of a card. Typically these
-///    would be styled using a [ButtonTheme] created with [new ButtonTheme.bar].
+///  * [ButtonBar], to display buttons at the bottom of a card.
 ///  * [showDialog], to display a modal card.
 ///  * <https://material.io/design/components/cards.html>
 class Card extends StatelessWidget {
diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart
index 7e3e985..bead9d4 100644
--- a/packages/flutter/lib/src/material/date_picker.dart
+++ b/packages/flutter/lib/src/material/date_picker.dart
@@ -11,7 +11,6 @@
 import 'package:flutter/gestures.dart' show DragStartBehavior;
 
 import 'button_bar.dart';
-import 'button_theme.dart';
 import 'colors.dart';
 import 'debug.dart';
 import 'dialog.dart';
@@ -986,19 +985,17 @@
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
     final Widget picker = _buildPicker();
-    final Widget actions = ButtonTheme.bar(
-      child: ButtonBar(
-        children: <Widget>[
-          FlatButton(
-            child: Text(localizations.cancelButtonLabel),
-            onPressed: _handleCancel,
-          ),
-          FlatButton(
-            child: Text(localizations.okButtonLabel),
-            onPressed: _handleOk,
-          ),
-        ],
-      ),
+    final Widget actions = ButtonBar(
+      children: <Widget>[
+        FlatButton(
+          child: Text(localizations.cancelButtonLabel),
+          onPressed: _handleCancel,
+        ),
+        FlatButton(
+          child: Text(localizations.okButtonLabel),
+          onPressed: _handleOk,
+        ),
+      ],
     );
 
     final Dialog dialog = Dialog(
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 36f0209..167c0ab 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -8,7 +8,6 @@
 import 'package:flutter/widgets.dart';
 
 import 'button_bar.dart';
-import 'button_theme.dart';
 import 'colors.dart';
 import 'debug.dart';
 import 'dialog_theme.dart';
@@ -343,10 +342,8 @@
     }
 
     if (actions != null) {
-      children.add(ButtonTheme.bar(
-        child: ButtonBar(
-          children: actions,
-        ),
+      children.add(ButtonBar(
+        children: actions,
       ));
     }
 
diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart
index 0627791..3451015 100644
--- a/packages/flutter/lib/src/material/paginated_data_table.dart
+++ b/packages/flutter/lib/src/material/paginated_data_table.dart
@@ -9,7 +9,6 @@
 import 'package:flutter/gestures.dart' show DragStartBehavior;
 
 import 'button_bar.dart';
-import 'button_theme.dart';
 import 'card.dart';
 import 'constants.dart';
 import 'data_table.dart';
@@ -439,16 +438,14 @@
                 data: const IconThemeData(
                   opacity: 0.54
                 ),
-                child: ButtonTheme.bar(
-                  child: Ink(
-                    height: 64.0,
-                    color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
-                    child: Padding(
-                      padding: EdgeInsetsDirectional.only(start: startPadding, end: 14.0),
-                      child: Row(
-                        mainAxisAlignment: MainAxisAlignment.end,
-                        children: headerWidgets,
-                      ),
+                child: Ink(
+                  height: 64.0,
+                  color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
+                  child: Padding(
+                    padding: EdgeInsetsDirectional.only(start: startPadding, end: 14.0),
+                    child: Row(
+                      mainAxisAlignment: MainAxisAlignment.end,
+                      children: headerWidgets,
                     ),
                   ),
                 ),
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index d71d76a..4488d8e 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -14,7 +14,6 @@
 import 'app_bar.dart';
 import 'bottom_sheet.dart';
 import 'button_bar.dart';
-import 'button_theme.dart';
 import 'colors.dart';
 import 'divider.dart';
 import 'drawer.dart';
@@ -2154,13 +2153,9 @@
             ),
           ),
           child: SafeArea(
-            child: ButtonTheme.bar(
-              child: SafeArea(
-                top: false,
-                child: ButtonBar(
-                  children: widget.persistentFooterButtons,
-                ),
-              ),
+            top: false,
+            child: ButtonBar(
+              children: widget.persistentFooterButtons,
             ),
           ),
         ),
diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart
index e5e6398..db70eec 100644
--- a/packages/flutter/lib/src/material/snack_bar.dart
+++ b/packages/flutter/lib/src/material/snack_bar.dart
@@ -301,9 +301,10 @@
       ),
     ];
     if (action != null) {
-      children.add(ButtonTheme.bar(
-        padding: EdgeInsets.symmetric(horizontal: snackBarPadding),
+      children.add(ButtonTheme(
         textTheme: ButtonTextTheme.accent,
+        minWidth: 64.0,
+        padding: EdgeInsets.symmetric(horizontal: snackBarPadding),
         child: action,
       ));
     } else {
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 981a387..dacad32 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -13,6 +13,7 @@
 import 'banner_theme.dart';
 import 'bottom_app_bar_theme.dart';
 import 'bottom_sheet_theme.dart';
+import 'button_bar_theme.dart';
 import 'button_theme.dart';
 import 'card_theme.dart';
 import 'chip_theme.dart';
@@ -180,6 +181,7 @@
     PopupMenuThemeData popupMenuTheme,
     MaterialBannerThemeData bannerTheme,
     DividerThemeData dividerTheme,
+    ButtonBarThemeData buttonBarTheme,
   }) {
     brightness ??= Brightness.light;
     final bool isDark = brightness == Brightness.dark;
@@ -285,6 +287,7 @@
     popupMenuTheme ??= const PopupMenuThemeData();
     bannerTheme ??= const MaterialBannerThemeData();
     dividerTheme ??= const DividerThemeData();
+    buttonBarTheme ??= const ButtonBarThemeData();
 
     return ThemeData.raw(
       brightness: brightness,
@@ -348,6 +351,7 @@
       popupMenuTheme: popupMenuTheme,
       bannerTheme: bannerTheme,
       dividerTheme: dividerTheme,
+      buttonBarTheme: buttonBarTheme,
     );
   }
 
@@ -423,6 +427,7 @@
     @required this.popupMenuTheme,
     @required this.bannerTheme,
     @required this.dividerTheme,
+    @required this.buttonBarTheme,
   }) : assert(brightness != null),
        assert(primaryColor != null),
        assert(primaryColorBrightness != null),
@@ -480,7 +485,8 @@
        assert(bottomSheetTheme != null),
        assert(popupMenuTheme != null),
        assert(bannerTheme != null),
-       assert(dividerTheme != null);
+       assert(dividerTheme != null),
+       assert(buttonBarTheme != null);
 
   /// Create a [ThemeData] based on the colors in the given [colorScheme] and
   /// text styles of the optional [textTheme].
@@ -877,6 +883,9 @@
   /// [VerticalDivider]s, etc.
   final DividerThemeData dividerTheme;
 
+  /// A theme for customizing the appearance and layout of [ButtonBar] widgets.
+  final ButtonBarThemeData buttonBarTheme;
+
   /// Creates a copy of this theme but with the given fields replaced with the new values.
   ThemeData copyWith({
     Brightness brightness,
@@ -940,6 +949,7 @@
     PopupMenuThemeData popupMenuTheme,
     MaterialBannerThemeData bannerTheme,
     DividerThemeData dividerTheme,
+    ButtonBarThemeData buttonBarTheme,
   }) {
     cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
     return ThemeData.raw(
@@ -1004,6 +1014,7 @@
       popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme,
       bannerTheme: bannerTheme ?? this.bannerTheme,
       dividerTheme: dividerTheme ?? this.dividerTheme,
+      buttonBarTheme: buttonBarTheme ?? this.buttonBarTheme,
     );
   }
 
@@ -1146,6 +1157,7 @@
       popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t),
       bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
       dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
+      buttonBarTheme: ButtonBarThemeData.lerp(a.buttonBarTheme, b.buttonBarTheme, t),
     );
   }
 
@@ -1215,7 +1227,8 @@
            (otherData.bottomSheetTheme == bottomSheetTheme) &&
            (otherData.popupMenuTheme == popupMenuTheme) &&
            (otherData.bannerTheme == bannerTheme) &&
-           (otherData.dividerTheme == dividerTheme);
+           (otherData.dividerTheme == dividerTheme) &&
+           (otherData.buttonBarTheme == buttonBarTheme);
   }
 
   @override
@@ -1285,6 +1298,7 @@
       popupMenuTheme,
       bannerTheme,
       dividerTheme,
+      buttonBarTheme,
     ];
     return hashList(values);
   }
@@ -1351,6 +1365,7 @@
     properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme));
     properties.add(DiagnosticsProperty<MaterialBannerThemeData>('bannerTheme', bannerTheme, defaultValue: defaultData.bannerTheme));
     properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme));
+    properties.add(DiagnosticsProperty<ButtonBarThemeData>('buttonBarTheme', buttonBarTheme, defaultValue: defaultData.buttonBarTheme));
   }
 }
 
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index 8eb322b..d5cf674 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -10,7 +10,6 @@
 import 'package:flutter/widgets.dart';
 
 import 'button_bar.dart';
-import 'button_theme.dart';
 import 'colors.dart';
 import 'debug.dart';
 import 'dialog.dart';
@@ -1613,19 +1612,17 @@
       ),
     );
 
-    final Widget actions = ButtonTheme.bar(
-      child: ButtonBar(
-        children: <Widget>[
-          FlatButton(
-            child: Text(localizations.cancelButtonLabel),
-            onPressed: _handleCancel,
-          ),
-          FlatButton(
-            child: Text(localizations.okButtonLabel),
-            onPressed: _handleOk,
-          ),
-        ],
-      ),
+    final Widget actions = ButtonBar(
+      children: <Widget>[
+        FlatButton(
+          child: Text(localizations.cancelButtonLabel),
+          onPressed: _handleCancel,
+        ),
+        FlatButton(
+          child: Text(localizations.okButtonLabel),
+          onPressed: _handleOk,
+        ),
+      ],
     );
 
     final Dialog dialog = Dialog(
diff --git a/packages/flutter/test/material/button_bar_test.dart b/packages/flutter/test/material/button_bar_test.dart
index 2d18937..fa3c625 100644
--- a/packages/flutter/test/material/button_bar_test.dart
+++ b/packages/flutter/test/material/button_bar_test.dart
@@ -15,105 +15,282 @@
     );
   });
 
-  testWidgets('ButtonBar has a min height of 52 when using ButtonBarLayoutBehavior.constrained', (WidgetTester tester) async {
-    await tester.pumpWidget(
-      SingleChildScrollView(
-        child: ListBody(
-          children: <Widget>[
-            ButtonTheme.bar(
+  group('alignment', () {
+
+    testWidgets('default alignment is MainAxisAlignment.end', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        const MaterialApp(
+          home: ButtonBar(
+            children: <Widget>[
+              SizedBox(width: 10.0, height: 10.0),
+            ],
+          ),
+        )
+      );
+
+      final Finder child = find.byType(SizedBox);
+      // Should be positioned to the right of the bar,
+      expect(tester.getRect(child).left, 782.0);  // bar width - default padding - 10
+      expect(tester.getRect(child).right, 792.0); // bar width - default padding
+    });
+
+    testWidgets('ButtonBarTheme.alignment overrides default', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        const MaterialApp(
+          home: ButtonBarTheme(
+            data: ButtonBarThemeData(
+              alignment: MainAxisAlignment.center,
+            ),
+            child: ButtonBar(
+              children: <Widget>[
+                SizedBox(width: 10.0, height: 10.0),
+              ],
+            ),
+          ),
+        )
+      );
+
+      final Finder child = find.byType(SizedBox);
+      // Should be positioned in the center
+      expect(tester.getRect(child).left, 395.0);  // (bar width - padding) / 2 - 10 / 2
+      expect(tester.getRect(child).right, 405.0); // (bar width - padding) / 2 - 10 / 2 + 10
+    });
+
+    testWidgets('ButtonBar.alignment overrides ButtonBarTheme.alignment and default', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        const MaterialApp(
+          home: ButtonBarTheme(
+            data: ButtonBarThemeData(
+              alignment: MainAxisAlignment.center,
+            ),
+            child: ButtonBar(
+              alignment: MainAxisAlignment.start,
+              children: <Widget>[
+                SizedBox(width: 10.0, height: 10.0),
+              ],
+            ),
+          ),
+        )
+      );
+
+      final Finder child = find.byType(SizedBox);
+      // Should be positioned on the left
+      expect(tester.getRect(child).left, 8.0);   // padding
+      expect(tester.getRect(child).right, 18.0); // padding + 10
+    });
+
+  });
+
+  group('mainAxisSize', () {
+
+    testWidgets('default mainAxisSize is MainAxisSize.max', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBar(
+            children: <Widget>[
+              Container(),
+            ],
+          ),
+        )
+      );
+
+      // ButtonBar uses a Row internally to implement this
+      final Row row = tester.widget(find.byType(Row));
+      expect(row.mainAxisSize, equals(MainAxisSize.max));
+    });
+
+    testWidgets('ButtonBarTheme.mainAxisSize overrides default', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBarTheme(
+            data: const ButtonBarThemeData(
+              mainAxisSize: MainAxisSize.min,
+            ),
+            child: ButtonBar(
+              children: <Widget>[
+                Container(),
+              ],
+            ),
+          ),
+        )
+      );
+
+      // ButtonBar uses a Row internally to implement this
+      final Row row = tester.widget(find.byType(Row));
+      expect(row.mainAxisSize, equals(MainAxisSize.min));
+    });
+
+    testWidgets('ButtonBar.mainAxisSize overrides ButtonBarTheme.mainAxisSize and default', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBarTheme(
+            data: const ButtonBarThemeData(
+              mainAxisSize: MainAxisSize.min,
+            ),
+            child: ButtonBar(
+              mainAxisSize: MainAxisSize.max,
+              children: <Widget>[
+                Container(),
+              ],
+            ),
+          ),
+        )
+      );
+
+      // ButtonBar uses a Row internally to implement this
+      final Row row = tester.widget(find.byType(Row));
+      expect(row.mainAxisSize, equals(MainAxisSize.max));
+    });
+
+  });
+
+  group('button properies override ButtonTheme', () {
+
+    testWidgets('default button properties override ButtonTheme properties', (WidgetTester tester) async {
+      BuildContext capturedContext;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBar(
+            children: <Widget>[
+              Builder(builder: (BuildContext context) {
+                capturedContext = context;
+                return Container();
+              })
+            ],
+          ),
+        )
+      );
+      final ButtonThemeData buttonTheme = ButtonTheme.of(capturedContext);
+      expect(buttonTheme.textTheme, equals(ButtonTextTheme.primary));
+      expect(buttonTheme.minWidth, equals(64.0));
+      expect(buttonTheme.height, equals(36.0));
+      expect(buttonTheme.padding, equals(const EdgeInsets.symmetric(horizontal: 8.0)));
+      expect(buttonTheme.alignedDropdown, equals(false));
+      expect(buttonTheme.layoutBehavior, equals(ButtonBarLayoutBehavior.padded));
+    });
+
+    testWidgets('ButtonBarTheme button properties override defaults and ButtonTheme properties', (WidgetTester tester) async {
+      BuildContext capturedContext;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBarTheme(
+            data: const ButtonBarThemeData(
+              buttonTextTheme: ButtonTextTheme.primary,
+              buttonMinWidth: 42.0,
+              buttonHeight: 84.0,
+              buttonPadding: EdgeInsets.fromLTRB(10, 20, 30, 40),
+              buttonAlignedDropdown: true,
               layoutBehavior: ButtonBarLayoutBehavior.constrained,
-              child: const Directionality(
-                textDirection: TextDirection.ltr,
-                child: ButtonBar(
-                  children: <Widget>[
-                    SizedBox(width: 10.0, height: 10.0),
-                  ],
-                ),
-              ),
             ),
-          ],
-        ),
-      ),
-    );
+            child: ButtonBar(
+              children: <Widget>[
+                Builder(builder: (BuildContext context) {
+                  capturedContext = context;
+                  return Container();
+                })
+              ],
+            ),
+          ),
+        )
+      );
+      final ButtonThemeData buttonTheme = ButtonTheme.of(capturedContext);
+      expect(buttonTheme.textTheme, equals(ButtonTextTheme.primary));
+      expect(buttonTheme.minWidth, equals(42.0));
+      expect(buttonTheme.height, equals(84.0));
+      expect(buttonTheme.padding, equals(const EdgeInsets.fromLTRB(10, 20, 30, 40)));
+      expect(buttonTheme.alignedDropdown, equals(true));
+      expect(buttonTheme.layoutBehavior, equals(ButtonBarLayoutBehavior.constrained));
+    });
 
-    final Finder buttonBar = find.byType(ButtonBar);
-    expect(tester.getBottomRight(buttonBar).dy - tester.getTopRight(buttonBar).dy, 52.0);
-  });
-
-  testWidgets('ButtonBar has padding applied when using ButtonBarLayoutBehavior.padded', (WidgetTester tester) async {
-    await tester.pumpWidget(
-      SingleChildScrollView(
-        child: ListBody(
-          children: <Widget>[
-            ButtonTheme.bar(
+    testWidgets('ButtonBar button properties override ButtonBarTheme, defaults and ButtonTheme properties', (WidgetTester tester) async {
+      BuildContext capturedContext;
+      await tester.pumpWidget(
+        MaterialApp(
+          home: ButtonBarTheme(
+            data: const ButtonBarThemeData(
+              buttonTextTheme: ButtonTextTheme.accent,
+              buttonMinWidth: 4242.0,
+              buttonHeight: 8484.0,
+              buttonPadding: EdgeInsets.fromLTRB(50, 60, 70, 80),
+              buttonAlignedDropdown: false,
               layoutBehavior: ButtonBarLayoutBehavior.padded,
-              child: const Directionality(
+            ),
+            child: ButtonBar(
+              buttonTextTheme: ButtonTextTheme.primary,
+              buttonMinWidth: 42.0,
+              buttonHeight: 84.0,
+              buttonPadding: const EdgeInsets.fromLTRB(10, 20, 30, 40),
+              buttonAlignedDropdown: true,
+              layoutBehavior: ButtonBarLayoutBehavior.constrained,
+              children: <Widget>[
+                Builder(builder: (BuildContext context) {
+                  capturedContext = context;
+                  return Container();
+                })
+              ],
+            ),
+          ),
+        )
+      );
+      final ButtonThemeData buttonTheme = ButtonTheme.of(capturedContext);
+      expect(buttonTheme.textTheme, equals(ButtonTextTheme.primary));
+      expect(buttonTheme.minWidth, equals(42.0));
+      expect(buttonTheme.height, equals(84.0));
+      expect(buttonTheme.padding, equals(const EdgeInsets.fromLTRB(10, 20, 30, 40)));
+      expect(buttonTheme.alignedDropdown, equals(true));
+      expect(buttonTheme.layoutBehavior, equals(ButtonBarLayoutBehavior.constrained));
+    });
+
+  });
+
+  group('layoutBehavior', () {
+
+    testWidgets('ButtonBar has a min height of 52 when using ButtonBarLayoutBehavior.constrained', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        SingleChildScrollView(
+          child: ListBody(
+            children: const <Widget>[
+              Directionality(
                 textDirection: TextDirection.ltr,
                 child: ButtonBar(
+                  layoutBehavior: ButtonBarLayoutBehavior.constrained,
                   children: <Widget>[
                     SizedBox(width: 10.0, height: 10.0),
                   ],
                 ),
               ),
-            ),
-          ],
+            ],
+          ),
         ),
-      ),
-    );
+      );
 
-    final Finder buttonBar = find.byType(ButtonBar);
-    expect(tester.getBottomRight(buttonBar).dy - tester.getTopRight(buttonBar).dy, 26.0);
-  });
+      final Finder buttonBar = find.byType(ButtonBar);
+      expect(tester.getBottomRight(buttonBar).dy - tester.getTopRight(buttonBar).dy, 52.0);
+    });
 
-  testWidgets('ButtonBar FlatButton inherits Theme accentColor', (WidgetTester tester) async {
-    // Regression test for https://github.com/flutter/flutter/issues/22789
-
-    await tester.pumpWidget(
-      MaterialApp(
-        theme: ThemeData(accentColor: const Color(0x00000001)),
-        home: Builder(
-          builder: (BuildContext context) {
-            return Center(
-              child: ButtonTheme.bar(
+    testWidgets('ButtonBar has padding applied when using ButtonBarLayoutBehavior.padded', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        SingleChildScrollView(
+          child: ListBody(
+            children: const <Widget>[
+              Directionality(
+                textDirection: TextDirection.ltr,
                 child: ButtonBar(
+                  layoutBehavior: ButtonBarLayoutBehavior.padded,
                   children: <Widget>[
-                    FlatButton(
-                      child: const Text('button'),
-                      onPressed: () {
-                        showDialog<void>(
-                          context: context,
-                          builder: (BuildContext context) {
-                            return AlertDialog( // puts its actions in a ButtonBar
-                              actions: <Widget>[
-                                FlatButton(
-                                  onPressed: () { },
-                                  child: const Text('enabled'),
-                                ),
-                              ],
-                            );
-                          },
-                        );
-                      },
-                    ),
+                    SizedBox(width: 10.0, height: 10.0),
                   ],
                 ),
               ),
-            );
-          },
+            ],
+          ),
         ),
-      ),
-    );
+      );
 
-    expect(tester.widget<RawMaterialButton>(find.byType(RawMaterialButton)).textStyle.color, const Color(0x00000001));
+      final Finder buttonBar = find.byType(ButtonBar);
+      expect(tester.getBottomRight(buttonBar).dy - tester.getTopRight(buttonBar).dy, 26.0);
+    });
 
-    // Show the dialog
-    await tester.tap(find.text('button'));
-    await tester.pumpAndSettle();
-
-    final Finder dialogButton = find.ancestor(
-      of: find.text('enabled'),
-      matching: find.byType(RawMaterialButton),
-    );
-    expect(tester.widget<RawMaterialButton>(dialogButton).textStyle.color, const Color(0x00000001));
   });
+
 }
diff --git a/packages/flutter/test/material/button_bar_theme_test.dart b/packages/flutter/test/material/button_bar_theme_test.dart
new file mode 100644
index 0000000..d14da3a
--- /dev/null
+++ b/packages/flutter/test/material/button_bar_theme_test.dart
@@ -0,0 +1,150 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+
+  test('ButtonBarThemeData null fields by default', () {
+    const ButtonBarThemeData buttonBarTheme = ButtonBarThemeData();
+    expect(buttonBarTheme.alignment, null);
+    expect(buttonBarTheme.mainAxisSize, null);
+    expect(buttonBarTheme.buttonTextTheme, null);
+    expect(buttonBarTheme.buttonMinWidth, null);
+    expect(buttonBarTheme.buttonHeight, null);
+    expect(buttonBarTheme.buttonPadding, null);
+    expect(buttonBarTheme.buttonAlignedDropdown, null);
+    expect(buttonBarTheme.layoutBehavior, null);
+  });
+
+  test('ThemeData uses default ButtonBarThemeData', () {
+    expect(ThemeData().buttonBarTheme, equals(const ButtonBarThemeData()));
+  });
+
+  test('ButtonBarThemeData copyWith, ==, hashCode basics', () {
+    expect(const ButtonBarThemeData(), const ButtonBarThemeData().copyWith());
+    expect(const ButtonBarThemeData().hashCode, const ButtonBarThemeData().copyWith().hashCode);
+  });
+
+  testWidgets('ButtonBarThemeData lerps correctly', (WidgetTester tester) async {
+    const ButtonBarThemeData barThemePrimary = ButtonBarThemeData(
+      alignment: MainAxisAlignment.end,
+      mainAxisSize: MainAxisSize.min,
+      buttonTextTheme: ButtonTextTheme.primary,
+      buttonMinWidth: 20.0,
+      buttonHeight: 20.0,
+      buttonPadding: EdgeInsets.symmetric(vertical: 5.0),
+      buttonAlignedDropdown: false,
+      layoutBehavior: ButtonBarLayoutBehavior.padded,
+    );
+    const ButtonBarThemeData barThemeAccent = ButtonBarThemeData(
+      alignment: MainAxisAlignment.center,
+      mainAxisSize: MainAxisSize.max,
+      buttonTextTheme: ButtonTextTheme.accent,
+      buttonMinWidth: 10.0,
+      buttonHeight: 40.0,
+      buttonPadding: EdgeInsets.symmetric(horizontal: 10.0),
+      buttonAlignedDropdown: true,
+      layoutBehavior: ButtonBarLayoutBehavior.constrained,
+    );
+
+    final ButtonBarThemeData lerp = ButtonBarThemeData.lerp(barThemePrimary, barThemeAccent, 0.5);
+    expect(lerp.alignment, equals(MainAxisAlignment.center));
+    expect(lerp.mainAxisSize, equals(MainAxisSize.max));
+    expect(lerp.buttonTextTheme, equals(ButtonTextTheme.accent));
+    expect(lerp.buttonMinWidth, equals(15.0));
+    expect(lerp.buttonHeight, equals(30.0));
+    expect(lerp.buttonPadding, equals(const EdgeInsets.fromLTRB(5.0, 2.5, 5.0, 2.5)));
+    expect(lerp.buttonAlignedDropdown, isTrue);
+    expect(lerp.layoutBehavior, equals(ButtonBarLayoutBehavior.constrained));
+  });
+
+  testWidgets('Default ButtonBarThemeData debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    const ButtonBarThemeData().debugFillProperties(builder);
+
+    final List<String> description = builder.properties
+        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+        .map((DiagnosticsNode node) => node.toString())
+        .toList();
+
+    expect(description, <String>[]);
+  });
+
+  testWidgets('ButtonBarThemeData implements debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    const ButtonBarThemeData(
+      alignment: MainAxisAlignment.center,
+      mainAxisSize: MainAxisSize.max,
+      buttonTextTheme: ButtonTextTheme.accent,
+      buttonMinWidth: 10.0,
+      buttonHeight: 42.0,
+      buttonPadding: EdgeInsets.symmetric(horizontal: 7.3),
+      buttonAlignedDropdown: true,
+      layoutBehavior: ButtonBarLayoutBehavior.constrained,
+    ).debugFillProperties(builder);
+
+    final List<String> description = builder.properties
+        .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+        .map((DiagnosticsNode node) => node.toString())
+        .toList();
+
+    expect(description, <String>[
+      'alignment: MainAxisAlignment.center',
+      'mainAxisSize: MainAxisSize.max',
+      'textTheme: ButtonTextTheme.accent',
+      'minWidth: 10.0',
+      'height: 42.0',
+      'padding: EdgeInsets(7.3, 0.0, 7.3, 0.0)',
+      'dropdown width matches button',
+      'layoutBehavior: ButtonBarLayoutBehavior.constrained',
+    ]);
+  });
+
+  testWidgets('ButtonBarTheme.of falls back to ThemeData.buttonBarTheme', (WidgetTester tester) async {
+    const ButtonBarThemeData buttonBarTheme = ButtonBarThemeData(buttonMinWidth: 42.0);
+    BuildContext capturedContext;
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: ThemeData(buttonBarTheme: buttonBarTheme),
+        home: Builder(
+          builder: (BuildContext context) {
+            capturedContext = context;
+            return Container();
+          }
+        )
+      )
+    );
+    expect(ButtonBarTheme.of(capturedContext), equals(buttonBarTheme));
+    expect(ButtonBarTheme.of(capturedContext).buttonMinWidth, equals(42.0));
+  });
+
+  testWidgets('ButtonBarTheme overrides ThemeData.buttonBarTheme', (WidgetTester tester) async {
+    const ButtonBarThemeData defaultBarTheme = ButtonBarThemeData(buttonMinWidth: 42.0);
+    const ButtonBarThemeData buttonBarTheme = ButtonBarThemeData(buttonMinWidth: 84.0);
+    BuildContext capturedContext;
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: ThemeData(buttonBarTheme: defaultBarTheme),
+        home: Builder(
+          builder: (BuildContext context) {
+            return ButtonBarTheme(
+              data: buttonBarTheme,
+              child: Builder(
+                builder: (BuildContext context) {
+                  capturedContext = context;
+                  return Container();
+                },
+              ),
+            );
+          }
+        )
+      )
+    );
+    expect(ButtonBarTheme.of(capturedContext), equals(buttonBarTheme));
+    expect(ButtonBarTheme.of(capturedContext).buttonMinWidth, equals(84.0));
+  });
+}