Reland "Add DataTableTheme to allow for themable DataTables (#64316)" (#65383)

diff --git a/packages/flutter/lib/material.dart b/packages/flutter/lib/material.dart
index aa7c82b..1ef4259 100644
--- a/packages/flutter/lib/material.dart
+++ b/packages/flutter/lib/material.dart
@@ -52,6 +52,7 @@
 export 'src/material/curves.dart';
 export 'src/material/data_table.dart';
 export 'src/material/data_table_source.dart';
+export 'src/material/data_table_theme.dart';
 export 'src/material/debug.dart';
 export 'src/material/dialog.dart';
 export 'src/material/dialog_theme.dart';
diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart
index 98cc73d2..8bbff0f 100644
--- a/packages/flutter/lib/src/material/data_table.dart
+++ b/packages/flutter/lib/src/material/data_table.dart
@@ -11,7 +11,6 @@
 import 'package:flutter/widgets.dart';
 
 import 'checkbox.dart';
-import 'colors.dart';
 import 'constants.dart';
 import 'debug.dart';
 import 'divider.dart';
@@ -414,25 +413,26 @@
     this.sortColumnIndex,
     this.sortAscending = true,
     this.onSelectAll,
-    this.dataRowHeight = kMinInteractiveDimension,
-    this.headingRowHeight = 56.0,
-    this.horizontalMargin = 24.0,
-    this.columnSpacing = 56.0,
+    this.dataRowColor,
+    this.dataRowHeight,
+    this.dataTextStyle,
+    this.headingRowColor,
+    this.headingRowHeight,
+    this.headingTextStyle,
+    this.horizontalMargin,
+    this.columnSpacing,
     this.showCheckboxColumn = true,
-    this.dividerThickness = 1.0,
+    this.showBottomBorder = false,
+    this.dividerThickness,
     @required this.rows,
   }) : assert(columns != null),
        assert(columns.isNotEmpty),
        assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
        assert(sortAscending != null),
-       assert(dataRowHeight != null),
-       assert(headingRowHeight != null),
-       assert(horizontalMargin != null),
-       assert(columnSpacing != null),
        assert(showCheckboxColumn != null),
        assert(rows != null),
        assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
-       assert(dividerThickness != null && dividerThickness >= 0),
+       assert(dividerThickness == null || dividerThickness >= 0),
        _onlyTextColumn = _initOnlyTextColumn(columns),
        super(key: key);
 
@@ -475,27 +475,110 @@
   /// row is selectable.
   final ValueSetter<bool> onSelectAll;
 
-  /// The height of each row (excluding the row that contains column headings).
+  /// {@template flutter.material.dataTable.dataRowColor}
+  /// The background color for the data rows.
   ///
-  /// This value defaults to kMinInteractiveDimension to adhere to the Material
+  /// The effective background color can be made to depend on the
+  /// [MaterialState] state, i.e. if the row is selected, pressed, hovered,
+  /// focused, disabled or enabled. The color is painted as an overlay to the
+  /// row. To make sure that the row's [InkWell] is visible (when pressed,
+  /// hovered and focused), it is recommended to use a translucent background
+  /// color.
+  /// {@endtemplate}
+  ///
+  /// By default, the background color is transparent unless selected. Selected
+  /// rows have a grey translucent color. To set a different color for
+  /// individual rows, see [DataRow.color].
+  ///
+  /// {@template flutter.material.dataTable.dataRowColorCode}
+  /// ```dart
+  /// DataTable(
+  ///   dataRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+  ///     if (states.contains(MaterialState.selected))
+  ///       return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+  ///     return null;  // Use the default value.
+  ///   }),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  /// {@endtemplate}
+  final MaterialStateProperty<Color> dataRowColor;
+
+  /// {@template flutter.material.dataTable.dataRowHeight}
+  /// The height of each row (excluding the row that contains column headings).
+  /// {@endtemplate}
+  ///
+  /// This value defaults to [kMinInteractiveDimension] to adhere to the Material
   /// Design specifications.
   final double dataRowHeight;
 
+  /// {@template flutter.material.dataTable.dataTextStyle}
+  /// The text style for data rows.
+  /// {@endtemplate}
+  ///
+  /// By default, the text style is [TextTheme.bodyText2].
+  final TextStyle dataTextStyle;
+
+  /// {@template flutter.material.dataTable.headingRowColor}
+  /// The background color for the heading row.
+  ///
+  /// The effective background color can be made to depend on the
+  /// [MaterialState] state, i.e. if the row is pressed, hovered, focused when
+  /// sorted. The color is painted as an overlay to the row. To make sure that
+  /// the row's [InkWell] is visible (when pressed, hovered and focused), it is
+  /// recommended to use a translucent color.
+  ///
+  /// ```dart
+  /// DataTable(
+  ///   headingRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+  ///     if (states.contains(MaterialState.hovered))
+  ///       return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+  ///     return null;  // Use the default value.
+  ///   }),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  /// {@endtemplate}
+  final MaterialStateProperty<Color> headingRowColor;
+
+  /// {@template flutter.material.dataTable.headingRowHeight}
   /// The height of the heading row.
+  /// {@endtemplate}
   ///
   /// This value defaults to 56.0 to adhere to the Material Design specifications.
   final double headingRowHeight;
 
+  /// {@template flutter.material.dataTable.headingTextStyle}
+  /// The text style for the heading row.
+  /// {@endtemplate}
+  ///
+  /// By default, the text style is [TextTheme.subtitle2].
+  final TextStyle headingTextStyle;
+
+  /// {@template flutter.material.dataTable.horizontalMargin}
   /// The horizontal margin between the edges of the table and the content
   /// in the first and last cells of each row.
   ///
   /// When a checkbox is displayed, it is also the margin between the checkbox
   /// the content in the first data column.
+  /// {@endtemplate}
   ///
   /// This value defaults to 24.0 to adhere to the Material Design specifications.
   final double horizontalMargin;
 
+  /// {@template flutter.material.dataTable.columnSpacing}
   /// The horizontal margin between the contents of each data column.
+  /// {@endtemplate}
   ///
   /// This value defaults to 56.0 to adhere to the Material Design specifications.
   final double columnSpacing;
@@ -517,6 +600,20 @@
   /// Must be non-null, but may be empty.
   final List<DataRow> rows;
 
+  /// {@template flutter.material.dataTable.dividerThickness}
+  /// The width of the divider that appears between [TableRow]s.
+  ///
+  /// Must be greater than or equal to zero.
+  /// {@endtemplate}
+  /// This value defaults to 1.0.
+  final double dividerThickness;
+
+  /// Whether a border at the bottom of the table is displayed.
+  ///
+  /// By default, a border is not shown at the bottom to allow for a border
+  /// around the table.
+  final bool showBottomBorder;
+
   // Set by the constructor to the index of the only Column that is
   // non-numeric, if there is exactly one, otherwise null.
   final int _onlyTextColumn;
@@ -551,29 +648,45 @@
     }
   }
 
+  /// The default height of the heading row.
+  static const double _headingRowHeight = 56.0;
+
+  /// The default horizontal margin between the edges of the table and the content
+  /// in the first and last cells of each row.
+  static const double _horizontalMargin = 24.0;
+
+  /// The default horizontal margin between the contents of each data column.
+  static const double _columnSpacing = 56.0;
+
+  /// The default padding between the heading content and sort arrow.
   static const double _sortArrowPadding = 2.0;
-  static const double _headingFontSize = 12.0;
+
+  /// The default divider thickness.
+  static const double _dividerThickness = 1.0;
+
   static const Duration _sortArrowAnimationDuration = Duration(milliseconds: 150);
   static const Color _grey100Opacity = Color(0x0A000000); // Grey 100 as opacity instead of solid color
   static const Color _grey300Opacity = Color(0x1E000000); // Dark theme variant is just a guess.
 
-  /// The width of the divider that appears between [TableRow]s.
-  ///
-  /// Must be non-null and greater than or equal to zero.
-  /// This value defaults to 1.0.
-  final double dividerThickness;
-
   Widget _buildCheckbox({
+    BuildContext context,
     Color activeColor,
     bool checked,
     VoidCallback onRowTap,
     ValueChanged<bool> onCheckboxChanged,
     MaterialStateProperty<Color> overlayColor,
   }) {
+    final ThemeData themeData = Theme.of(context);
+    final double effectiveHorizontalMargin = horizontalMargin
+      ?? themeData.dataTableTheme.horizontalMargin
+      ?? _horizontalMargin;
     Widget contents = Semantics(
       container: true,
       child: Padding(
-        padding: EdgeInsetsDirectional.only(start: horizontalMargin, end: horizontalMargin / 2.0),
+        padding: EdgeInsetsDirectional.only(
+          start: effectiveHorizontalMargin,
+          end: effectiveHorizontalMargin / 2.0,
+        ),
         child: Center(
           child: Checkbox(
             activeColor: activeColor,
@@ -605,39 +718,37 @@
     VoidCallback onSort,
     bool sorted,
     bool ascending,
+    MaterialStateProperty<Color> overlayColor,
   }) {
-    List<Widget> arrowWithPadding() {
-      return onSort == null ? const <Widget>[] : <Widget>[
-        _SortArrow(
-          visible: sorted,
-          up: sorted ? ascending : null,
-          duration: _sortArrowAnimationDuration,
-        ),
-        const SizedBox(width: _sortArrowPadding),
-      ];
-    }
+    final ThemeData themeData = Theme.of(context);
     label = Row(
       textDirection: numeric ? TextDirection.rtl : null,
       children: <Widget>[
         label,
-        ...arrowWithPadding(),
+        if (onSort != null)
+          ...<Widget>[
+            _SortArrow(
+              visible: sorted,
+              up: sorted ? ascending : null,
+              duration: _sortArrowAnimationDuration,
+            ),
+            const SizedBox(width: _sortArrowPadding),
+          ],
       ],
     );
+
+    final TextStyle effectiveHeadingTextStyle = headingTextStyle
+      ?? themeData.dataTableTheme.headingTextStyle
+      ?? themeData.textTheme.subtitle2;
+    final double effectiveHeadingRowHeight = headingRowHeight
+      ?? themeData.dataTableTheme.headingRowHeight
+      ?? _headingRowHeight;
     label = Container(
       padding: padding,
-      height: headingRowHeight,
+      height: effectiveHeadingRowHeight,
       alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
       child: AnimatedDefaultTextStyle(
-        style: TextStyle(
-          // TODO(hansmuller): This should use the information provided by
-          // textTheme/DataTableTheme, https://github.com/flutter/flutter/issues/56079
-          fontWeight: FontWeight.w500,
-          fontSize: _headingFontSize,
-          height: math.min(1.0, headingRowHeight / _headingFontSize),
-          color: (Theme.of(context).brightness == Brightness.light)
-            ? ((onSort != null && sorted) ? Colors.black87 : Colors.black54)
-            : ((onSort != null && sorted) ? Colors.white : Colors.white70),
-        ),
+        style: effectiveHeadingTextStyle,
         softWrap: false,
         duration: _sortArrowAnimationDuration,
         child: label,
@@ -649,10 +760,12 @@
         child: label,
       );
     }
+
     // TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by
     // https://github.com/flutter/flutter/issues/51152
     label = InkWell(
       onTap: onSort,
+      overlayColor: overlayColor,
       child: label,
     );
     return label;
@@ -669,7 +782,7 @@
     VoidCallback onSelectChanged,
     MaterialStateProperty<Color> overlayColor,
   }) {
-    final bool isLightTheme = Theme.of(context).brightness == Brightness.light;
+    final ThemeData themeData = Theme.of(context);
     if (showEditIcon) {
       const Widget icon = Icon(Icons.edit, size: 18.0);
       label = Expanded(child: label);
@@ -678,25 +791,22 @@
         children: <Widget>[ label, icon ],
       );
     }
+
+    final TextStyle effectiveDataTextStyle = dataTextStyle
+      ?? themeData.dataTableTheme.dataTextStyle
+      ?? themeData.textTheme.bodyText2;
+    final double effectiveDataRowHeight = dataRowHeight
+      ?? themeData.dataTableTheme.dataRowHeight
+      ?? kMinInteractiveDimension;
     label = Container(
       padding: padding,
-      height: dataRowHeight,
+      height: effectiveDataRowHeight,
       alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
       child: DefaultTextStyle(
-        style: TextStyle(
-          // TODO(hansmuller): This should use the information provided by
-          // textTheme/DataTableTheme, https://github.com/flutter/flutter/issues/56079
-          fontSize: 13.0,
-          color: isLightTheme
-            ? (placeholder ? Colors.black38 : Colors.black87)
-            : (placeholder ? Colors.white38 : Colors.white70),
+        style: effectiveDataTextStyle.copyWith(
+          color: placeholder ? effectiveDataTextStyle.color.withOpacity(0.6) : null,
         ),
-        child: IconTheme.merge(
-          data: IconThemeData(
-            color: isLightTheme ? Colors.black54 : Colors.white70,
-          ),
-          child: DropdownButtonHideUnderline(child: label),
-        ),
+        child: DropdownButtonHideUnderline(child: label),
       ),
     );
     if (onTap != null) {
@@ -720,12 +830,16 @@
     assert(!_debugInteractive || debugCheckHasMaterial(context));
 
     final ThemeData theme = Theme.of(context);
+    final MaterialStateProperty<Color> effectiveHeadingRowColor = headingRowColor
+      ?? theme.dataTableTheme.headingRowColor;
+    final MaterialStateProperty<Color> effectiveDataRowColor = dataRowColor
+      ?? theme.dataTableTheme.dataRowColor;
     final MaterialStateProperty<Color> defaultRowColor = MaterialStateProperty.resolveWith(
       (Set<MaterialState> states) {
         if (states.contains(MaterialState.selected)) {
-          // TODO(per): Add theming support for DataTable, https://github.com/flutter/flutter/issues/56079.
           // The color has to be transparent so you can see the ink on
           // the [Material].
+          // TODO(perclasson): Align with Material specs, use translucent primary color: https://github.com/flutter/flutter/issues/64314.
           return (Theme.of(context).brightness == Brightness.light) ?
             _grey100Opacity : _grey300Opacity;
         }
@@ -735,6 +849,12 @@
     final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null);
     final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable;
     final bool allChecked = displayCheckboxColumn && !rows.any((DataRow row) => row.onSelectChanged != null && !row.selected);
+    final double effectiveHorizontalMargin = horizontalMargin
+      ?? theme.dataTableTheme.horizontalMargin
+      ?? _horizontalMargin;
+    final double effectiveColumnSpacing = columnSpacing
+      ?? theme.dataTableTheme.columnSpacing
+      ?? _columnSpacing;
 
     final List<TableColumnWidth> tableColumns = List<TableColumnWidth>(columns.length + (displayCheckboxColumn ? 1 : 0));
     final List<TableRow> tableRows = List<TableRow>.generate(
@@ -748,13 +868,22 @@
           if (isDisabled)
             MaterialState.disabled,
         };
-        final Color rowColor = index > 0 ? rows[index - 1].color?.resolve(states) : null;
+        final Color resolvedDataRowColor = index > 0 ? (rows[index - 1].color ?? effectiveDataRowColor)?.resolve(states) : null;
+        final Color resolvedHeadingRowColor = effectiveHeadingRowColor?.resolve(<MaterialState>{});
+        final Color rowColor = index > 0 ? resolvedDataRowColor : resolvedHeadingRowColor;
+        final BorderSide borderSide = Divider.createBorderSide(
+          context,
+          width: dividerThickness
+            ?? theme.dataTableTheme.dividerThickness
+            ?? _dividerThickness,
+        );
+        final Border border = showBottomBorder
+          ? Border(bottom: borderSide)
+          : index == 0 ? null : Border(top: borderSide);
         return TableRow(
           key: index == 0 ? _headingRowKey : rows[index - 1].key,
           decoration: BoxDecoration(
-            border: Border(
-              bottom: Divider.createBorderSide(context, width: dividerThickness),
-            ),
+            border: border,
             color: rowColor ?? defaultRowColor.resolve(states),
           ),
           children: List<Widget>(tableColumns.length),
@@ -766,8 +895,9 @@
 
     int displayColumnIndex = 0;
     if (displayCheckboxColumn) {
-      tableColumns[0] = FixedColumnWidth(horizontalMargin + Checkbox.width + horizontalMargin / 2.0);
+      tableColumns[0] = FixedColumnWidth(effectiveHorizontalMargin + Checkbox.width + effectiveHorizontalMargin / 2.0);
       tableRows[0].children[0] = _buildCheckbox(
+        context: context,
         activeColor: theme.accentColor,
         checked: allChecked,
         onCheckboxChanged: _handleSelectAll,
@@ -775,11 +905,12 @@
       rowIndex = 1;
       for (final DataRow row in rows) {
         tableRows[rowIndex].children[0] = _buildCheckbox(
+          context: context,
           activeColor: theme.accentColor,
           checked: row.selected,
           onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null ,
           onCheckboxChanged: row.onSelectChanged,
-          overlayColor: row.color,
+          overlayColor: row.color ?? effectiveDataRowColor,
         );
         rowIndex += 1;
       }
@@ -791,18 +922,18 @@
 
       double paddingStart;
       if (dataColumnIndex == 0 && displayCheckboxColumn) {
-        paddingStart = horizontalMargin / 2.0;
+        paddingStart = effectiveHorizontalMargin / 2.0;
       } else if (dataColumnIndex == 0 && !displayCheckboxColumn) {
-        paddingStart = horizontalMargin;
+        paddingStart = effectiveHorizontalMargin;
       } else {
-        paddingStart = columnSpacing / 2.0;
+        paddingStart = effectiveColumnSpacing / 2.0;
       }
 
       double paddingEnd;
       if (dataColumnIndex == columns.length - 1) {
-        paddingEnd = horizontalMargin;
+        paddingEnd = effectiveHorizontalMargin;
       } else {
-        paddingEnd = columnSpacing / 2.0;
+        paddingEnd = effectiveColumnSpacing / 2.0;
       }
 
       final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
@@ -823,6 +954,7 @@
         onSort: column.onSort != null ? () => column.onSort(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null,
         sorted: dataColumnIndex == sortColumnIndex,
         ascending: sortAscending,
+        overlayColor: effectiveHeadingRowColor,
       );
       rowIndex = 1;
       for (final DataRow row in rows) {
@@ -836,7 +968,7 @@
           showEditIcon: cell.showEditIcon,
           onTap: cell.onTap,
           onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged(!row.selected) : null,
-          overlayColor: row.color,
+          overlayColor: row.color ?? effectiveDataRowColor,
         );
         rowIndex += 1;
       }
@@ -1035,10 +1167,9 @@
         transform: Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value)
                              ..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0),
         alignment: Alignment.center,
-        child: Icon(
+        child: const Icon(
           Icons.arrow_upward,
           size: _arrowIconSize,
-          color: (Theme.of(context).brightness == Brightness.light) ? Colors.black87 : Colors.white70,
         ),
       ),
     );
diff --git a/packages/flutter/lib/src/material/data_table_theme.dart b/packages/flutter/lib/src/material/data_table_theme.dart
new file mode 100644
index 0000000..708d779
--- /dev/null
+++ b/packages/flutter/lib/src/material/data_table_theme.dart
@@ -0,0 +1,240 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'dart:ui' show lerpDouble;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+import 'material_state.dart';
+import 'theme.dart';
+
+/// Defines default property values for descendant [DataTable]
+/// widgets.
+///
+/// Descendant widgets obtain the current [DataTableThemeData] object
+/// using `DataTableTheme.of(context)`. Instances of
+/// [DataTableThemeData] can be customized with
+/// [DataTableThemeData.copyWith].
+///
+/// Typically a [DataTableThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.dataTableTheme].
+///
+/// All [DataTableThemeData] properties are `null` by default. When
+/// null, the [DataTable] will use the values from [ThemeData] if they exist,
+/// otherwise it will provide its own defaults based on the overall [Theme]'s
+/// textTheme and colorScheme. See the individual [DataTable] properties for
+/// details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class DataTableThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.dataTableTheme].
+  const DataTableThemeData({
+    this.dataRowColor,
+    this.dataRowHeight,
+    this.dataTextStyle,
+    this.headingRowColor,
+    this.headingRowHeight,
+    this.headingTextStyle,
+    this.horizontalMargin,
+    this.columnSpacing,
+    this.dividerThickness,
+  });
+
+  /// {@macro flutter.material.dataTable.dataRowColor}
+  /// {@macro flutter.material.dataTable.dataRowColorCode}
+  final MaterialStateProperty<Color> dataRowColor;
+
+  /// {@macro flutter.material.dataTable.dataRowHeight}
+  final double dataRowHeight;
+
+  /// {@macro flutter.material.dataTable.dataTextStyle}
+  final TextStyle dataTextStyle;
+
+  /// {@macro flutter.material.dataTable.headingRowColor}
+  final MaterialStateProperty<Color> headingRowColor;
+
+  /// {@macro flutter.material.dataTable.headingRowHeight}
+  final double headingRowHeight;
+
+  /// {@macro flutter.material.dataTable.headingTextStyle}
+  final TextStyle headingTextStyle;
+
+  /// {@macro flutter.material.dataTable.horizontalMargin}
+  final double horizontalMargin;
+
+  /// {@macro flutter.material.dataTable.columnSpacing}
+  final double columnSpacing;
+
+  /// {@macro flutter.material.dataTable.dividerThickness}
+  final double dividerThickness;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  DataTableThemeData copyWith({
+    MaterialStateProperty<Color> dataRowColor,
+    double dataRowHeight,
+    TextStyle dataTextStyle,
+    MaterialStateProperty<Color> headingRowColor,
+    double headingRowHeight,
+    TextStyle headingTextStyle,
+    double horizontalMargin,
+    double columnSpacing,
+    double dividerThickness,
+  }) {
+    return DataTableThemeData(
+      dataRowColor: dataRowColor ?? this.dataRowColor,
+      dataRowHeight: dataRowHeight ?? this.dataRowHeight,
+      dataTextStyle: dataTextStyle ?? this.dataTextStyle,
+      headingRowColor: headingRowColor ?? this.headingRowColor,
+      headingRowHeight: headingRowHeight ?? this.headingRowHeight,
+      headingTextStyle: headingTextStyle ?? this.headingTextStyle,
+      horizontalMargin: horizontalMargin ?? this.horizontalMargin,
+      columnSpacing: columnSpacing ?? this.columnSpacing,
+      dividerThickness: dividerThickness ?? this.dividerThickness,
+    );
+  }
+
+  /// Linearly interpolate between two [DataTableThemeData]s.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) {
+    assert(t != null);
+    return DataTableThemeData(
+      dataRowColor: _lerpProperties(a.dataRowColor, b.dataRowColor, t, Color.lerp),
+      dataRowHeight: lerpDouble(a.dataRowHeight, b.dataRowHeight, t),
+      dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t),
+      headingRowColor: _lerpProperties(a.headingRowColor, b.headingRowColor, t, Color.lerp),
+      headingRowHeight: lerpDouble(a.headingRowHeight, b.headingRowHeight, t),
+      headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t),
+      horizontalMargin: lerpDouble(a.horizontalMargin, b.horizontalMargin, t),
+      columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
+      dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t)
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      dataRowColor,
+      dataRowHeight,
+      dataTextStyle,
+      headingRowColor,
+      headingRowHeight,
+      headingTextStyle,
+      horizontalMargin,
+      columnSpacing,
+      dividerThickness,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DataTableThemeData
+      && other.dataRowColor == dataRowColor
+      && other.dataRowHeight == dataRowHeight
+      && other.dataTextStyle == dataTextStyle
+      && other.headingRowColor == headingRowColor
+      && other.headingRowHeight == headingRowHeight
+      && other.headingTextStyle == headingTextStyle
+      && other.horizontalMargin == horizontalMargin
+      && other.columnSpacing == columnSpacing
+      && other.dividerThickness == dividerThickness;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('dataRowColor', dataRowColor, defaultValue: null));
+    properties.add(DoubleProperty('dataRowHeight', dataRowHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('dataTextStyle', dataTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('headingRowColor', headingRowColor, defaultValue: null));
+    properties.add(DoubleProperty('headingRowHeight', headingRowHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('headingTextStyle', headingTextStyle, defaultValue: null));
+    properties.add(DoubleProperty('horizontalMargin', horizontalMargin, defaultValue: null));
+    properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
+    properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
+  }
+
+  static MaterialStateProperty<T> _lerpProperties<T>(MaterialStateProperty<T> a, MaterialStateProperty<T> b, double t, T Function(T, T, double) lerpFunction ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T> a;
+  final MaterialStateProperty<T> b;
+  final double t;
+  final T Function(T, T, double) lerpFunction;
+
+  @override
+  T resolve(Set<MaterialState> states) {
+    final T resolvedA = a?.resolve(states);
+    final T resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+/// Applies a data table theme to descendant [DataTable] widgets.
+///
+/// Descendant widgets obtain the current theme's [DataTableTheme] object using
+/// [DataTableTheme.of]. When a widget uses [DataTableTheme.of], it is
+/// automatically rebuilt if the theme later changes.
+///
+/// A data table theme can be specified as part of the overall Material
+/// theme using [ThemeData.dataTableTheme].
+///
+/// See also:
+///
+///  * [DataTableThemeData], which describes the actual configuration
+///    of a data table theme.
+class DataTableTheme extends InheritedWidget {
+  /// Constructs a data table theme that configures all descendant
+  /// [DataTable] widgets.
+  ///
+  /// The [data] must not be null.
+  const DataTableTheme({
+    Key key,
+    @required this.data,
+    Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties used for all descendant [DataTable] widgets.
+  final DataTableThemeData data;
+
+  /// Returns the configuration [data] from the closest
+  /// [DataTableTheme] ancestor. If there is no ancestor, it returns
+  /// [ThemeData.dataTableTheme]. Applications can assume that the
+  /// returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DataTableThemeData theme = DataTableTheme.of(context);
+  /// ```
+  static DataTableThemeData of(BuildContext context) {
+    final DataTableTheme dataTableTheme = context.dependOnInheritedWidgetOfExactType<DataTableTheme>();
+    return dataTableTheme?.data ?? Theme.of(context).dataTableTheme;
+  }
+
+  @override
+  bool updateShouldNotify(DataTableTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart
index a0e9937..1269a42 100644
--- a/packages/flutter/lib/src/material/paginated_data_table.dart
+++ b/packages/flutter/lib/src/material/paginated_data_table.dart
@@ -476,6 +476,7 @@
                     horizontalMargin: widget.horizontalMargin,
                     columnSpacing: widget.columnSpacing,
                     showCheckboxColumn: widget.showCheckboxColumn,
+                    showBottomBorder: true,
                     rows: _getRows(_firstRowIndex, widget.rowsPerPage),
                   ),
                 ),
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 389d21f..566193e 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -22,6 +22,7 @@
 import 'chip_theme.dart';
 import 'color_scheme.dart';
 import 'colors.dart';
+import 'data_table_theme.dart';
 import 'dialog_theme.dart';
 import 'divider_theme.dart';
 import 'elevated_button_theme.dart';
@@ -283,6 +284,7 @@
     ElevatedButtonThemeData elevatedButtonTheme,
     OutlinedButtonThemeData outlinedButtonTheme,
     TextSelectionThemeData textSelectionTheme,
+    DataTableThemeData dataTableTheme,
     bool fixTextFieldOutlineLabel,
     bool useTextSelectionTheme,
   }) {
@@ -400,6 +402,7 @@
     elevatedButtonTheme ??= const ElevatedButtonThemeData();
     outlinedButtonTheme ??= const OutlinedButtonThemeData();
     textSelectionTheme ??= const TextSelectionThemeData();
+    dataTableTheme ??= const DataTableThemeData();
 
     fixTextFieldOutlineLabel ??= false;
     useTextSelectionTheme ??= false;
@@ -475,6 +478,7 @@
       elevatedButtonTheme: elevatedButtonTheme,
       outlinedButtonTheme: outlinedButtonTheme,
       textSelectionTheme: textSelectionTheme,
+      dataTableTheme: dataTableTheme,
       fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
       useTextSelectionTheme: useTextSelectionTheme,
     );
@@ -561,6 +565,7 @@
     @required this.elevatedButtonTheme,
     @required this.outlinedButtonTheme,
     @required this.textSelectionTheme,
+    @required this.dataTableTheme,
     @required this.fixTextFieldOutlineLabel,
     @required this.useTextSelectionTheme,
   }) : assert(visualDensity != null),
@@ -629,8 +634,8 @@
        assert(textButtonTheme != null),
        assert(elevatedButtonTheme != null),
        assert(outlinedButtonTheme != null),
-       assert(fixTextFieldOutlineLabel != null),
        assert(textSelectionTheme != null),
+       assert(dataTableTheme != null),
        assert(fixTextFieldOutlineLabel != null),
        assert(useTextSelectionTheme != null);
 
@@ -1107,6 +1112,10 @@
   /// A theme for customizing the appearance and layout of [TextField] widgets.
   final TextSelectionThemeData textSelectionTheme;
 
+  /// A theme for customizing the appearance and layout of [DataTable]
+  /// widgets.
+  final DataTableThemeData dataTableTheme;
+
   /// A temporary flag to allow apps to opt-in to a
   /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
   /// coordinate of the floating label in a [TextField] [OutlineInputBorder].
@@ -1206,6 +1215,7 @@
     ElevatedButtonThemeData elevatedButtonTheme,
     OutlinedButtonThemeData outlinedButtonTheme,
     TextSelectionThemeData textSelectionTheme,
+    DataTableThemeData dataTableTheme,
     bool fixTextFieldOutlineLabel,
     bool useTextSelectionTheme,
   }) {
@@ -1281,6 +1291,7 @@
       elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme,
       outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
       textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
+      dataTableTheme: dataTableTheme ?? this.dataTableTheme,
       fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
       useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
     );
@@ -1434,6 +1445,7 @@
       elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t),
       outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t),
       textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t),
+      dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
       fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
       useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
     );
@@ -1515,6 +1527,7 @@
         && other.elevatedButtonTheme == elevatedButtonTheme
         && other.outlinedButtonTheme == outlinedButtonTheme
         && other.textSelectionTheme == textSelectionTheme
+        && other.dataTableTheme == dataTableTheme
         && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
         && other.useTextSelectionTheme == useTextSelectionTheme;
   }
@@ -1595,6 +1608,7 @@
       elevatedButtonTheme,
       outlinedButtonTheme,
       textSelectionTheme,
+      dataTableTheme,
       fixTextFieldOutlineLabel,
       useTextSelectionTheme,
     ];
@@ -1673,6 +1687,7 @@
     properties.add(DiagnosticsProperty<TextButtonThemeData>('textButtonTheme', textButtonTheme, defaultValue: defaultData.textButtonTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));
   }
 }
 
diff --git a/packages/flutter/test/material/data_table_test.dart b/packages/flutter/test/material/data_table_test.dart
index eee3175..e7edfeb 100644
--- a/packages/flutter/test/material/data_table_test.dart
+++ b/packages/flutter/test/material/data_table_test.dart
@@ -177,6 +177,10 @@
       MaterialApp(
         home: Material(
           child: DataTable(
+            headingTextStyle: const TextStyle(
+              fontSize: 14.0,
+              letterSpacing: 0.0, // Will overflow if letter spacing is larger than 0.0.
+            ),
             columns: <DataColumn>[
               DataColumn(
                 label: Text('X' * 2000),
@@ -195,6 +199,7 @@
         ),
       ),
     );
+
     expect(tester.renderObject<RenderBox>(find.byType(Text).first).size.width, greaterThan(800.0));
     expect(tester.renderObject<RenderBox>(find.byType(Row).first).size.width, greaterThan(800.0));
     expect(tester.takeException(), isNull); // column overflows table, but text doesn't overflow cell
@@ -692,7 +697,7 @@
     );
 
     // custom first column padding
-    padding = find.widgetWithText(Padding, 'Frozen yogurt');
+    padding = find.widgetWithText(Padding, 'Frozen yogurt').first;
     cellContent = find.widgetWithText(Align, 'Frozen yogurt'); // DataTable wraps its DataCells in an Align widget
     expect(
       tester.getRect(cellContent).left - tester.getRect(padding).left,
@@ -952,9 +957,9 @@
     );
 
     Table table = tester.widget(find.byType(Table));
-    TableRow tableRow = table.children.first;
+    TableRow tableRow = table.children.last;
     BoxDecoration boxDecoration = tableRow.decoration as BoxDecoration;
-    expect(boxDecoration.border.bottom.width, 1.0);
+    expect(boxDecoration.border.top.width, 1.0);
 
     const double thickness =  4.2;
     await tester.pumpWidget(
@@ -969,9 +974,58 @@
       ),
     );
     table = tester.widget(find.byType(Table));
-    tableRow = table.children.first;
+    tableRow = table.children.last;
     boxDecoration = tableRow.decoration as BoxDecoration;
-    expect(boxDecoration.border.bottom.width, thickness);
+    expect(boxDecoration.border.top.width, thickness);
+  });
+
+  testWidgets('DataTable set show bottom border', (WidgetTester tester) async {
+    const List<DataColumn> columns = <DataColumn>[
+      DataColumn(label: Text('column1')),
+      DataColumn(label: Text('column2')),
+    ];
+
+    const List<DataCell> cells = <DataCell>[
+      DataCell(Text('cell1')),
+      DataCell(Text('cell2')),
+    ];
+
+    const List<DataRow> rows = <DataRow>[
+      DataRow(cells: cells),
+      DataRow(cells: cells),
+    ];
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: DataTable(
+            showBottomBorder: true,
+            columns: columns,
+            rows: rows,
+          ),
+        ),
+      ),
+    );
+
+    Table table = tester.widget(find.byType(Table));
+    TableRow tableRow = table.children.last;
+    BoxDecoration boxDecoration = tableRow.decoration as BoxDecoration;
+    expect(boxDecoration.border.bottom.width, 1.0);
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: DataTable(
+            columns: columns,
+            rows: rows,
+          ),
+        ),
+      ),
+    );
+    table = tester.widget(find.byType(Table));
+    tableRow = table.children.last;
+    boxDecoration = tableRow.decoration as BoxDecoration;
+    expect(boxDecoration.border.bottom.width, 0.0);
   });
 
   testWidgets('DataTable column heading cell - with and without sorting', (WidgetTester tester) async {
@@ -1225,7 +1279,30 @@
     final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Content1')));
     await tester.pump(const Duration(milliseconds: 200)); // splash is well underway
     final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as RenderBox;
-    expect(box, paints..circle(x: 64.0, y: 24.0, color: pressedColor));
+    expect(box, paints..circle(x: 68.0, y: 24.0, color: pressedColor));
     await gesture.up();
   });
+
+  testWidgets('DataTable can render inside an AlertDialog', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Material(
+          child: AlertDialog(
+            content: DataTable(
+              columns: const <DataColumn>[
+                DataColumn(label: Text('Col1')),
+              ],
+              rows: const <DataRow>[
+                DataRow(cells: <DataCell>[DataCell(Text('1'))]),
+              ],
+            ),
+            scrollable: true,
+          ),
+        ),
+      ),
+    );
+
+    expect(tester.takeException(), isNull);
+  });
+
 }
diff --git a/packages/flutter/test/material/data_table_theme_test.dart b/packages/flutter/test/material/data_table_theme_test.dart
new file mode 100644
index 0000000..192c789
--- /dev/null
+++ b/packages/flutter/test/material/data_table_theme_test.dart
@@ -0,0 +1,252 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.8
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('DataTableThemeData copyWith, ==, hashCode basics', () {
+    expect(const DataTableThemeData(), const DataTableThemeData().copyWith());
+    expect(const DataTableThemeData().hashCode, const DataTableThemeData().copyWith().hashCode);
+  });
+
+  test('DataTableThemeData defaults', () {
+    const DataTableThemeData themeData = DataTableThemeData();
+    expect(themeData.dataRowColor, null);
+    expect(themeData.dataRowHeight, null);
+    expect(themeData.dataTextStyle, null);
+    expect(themeData.headingRowColor, null);
+    expect(themeData.headingRowHeight, null);
+    expect(themeData.headingTextStyle, null);
+    expect(themeData.horizontalMargin, null);
+    expect(themeData.columnSpacing, null);
+    expect(themeData.dividerThickness, null);
+
+    const DataTableTheme theme = DataTableTheme(data: DataTableThemeData());
+    expect(theme.data.dataRowColor, null);
+    expect(theme.data.dataRowHeight, null);
+    expect(theme.data.dataTextStyle, null);
+    expect(theme.data.headingRowColor, null);
+    expect(theme.data.headingRowHeight, null);
+    expect(theme.data.headingTextStyle, null);
+    expect(theme.data.horizontalMargin, null);
+    expect(theme.data.columnSpacing, null);
+    expect(theme.data.dividerThickness, null);
+  });
+
+  testWidgets('Default DataTableThemeData debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    const DataTableThemeData().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('DataTableThemeData implements debugFillProperties', (WidgetTester tester) async {
+    final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+    DataTableThemeData(
+      dataRowColor: MaterialStateProperty.resolveWith<Color>(
+        (Set<MaterialState> states) => const Color(0xfffffff1),
+      ),
+      dataRowHeight: 51.0,
+      dataTextStyle: const TextStyle(fontSize: 12.0),
+      headingRowColor: MaterialStateProperty.resolveWith<Color>(
+        (Set<MaterialState> states) => const Color(0xfffffff2),
+      ),
+      headingRowHeight: 52.0,
+      headingTextStyle:  const TextStyle(fontSize: 14.0),
+      horizontalMargin: 3.0,
+      columnSpacing: 4.0,
+      dividerThickness: 5.0,
+    ).debugFillProperties(builder);
+
+    final List<String> description = builder.properties
+      .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
+      .map((DiagnosticsNode node) => node.toString())
+      .toList();
+
+    expect(description[0], 'dataRowColor: Instance of \'_MaterialStatePropertyWith<Color>\'');
+    expect(description[1], 'dataRowHeight: 51.0');
+    expect(description[2], 'dataTextStyle: TextStyle(inherit: true, size: 12.0)');
+    expect(description[3], 'headingRowColor: Instance of \'_MaterialStatePropertyWith<Color>\'');
+    expect(description[4], 'headingRowHeight: 52.0');
+    expect(description[5], 'headingTextStyle: TextStyle(inherit: true, size: 14.0)');
+    expect(description[6], 'horizontalMargin: 3.0');
+    expect(description[7], 'columnSpacing: 4.0');
+    expect(description[8], 'dividerThickness: 5.0');
+  });
+
+  testWidgets('DataTable is themeable', (WidgetTester tester) async {
+    final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff1),
+    );
+    const double dataRowHeight = 51.0;
+    const TextStyle dataTextStyle = TextStyle(fontSize: 12.5);
+    final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff2),
+    );
+    const double headingRowHeight = 52.0;
+    const TextStyle headingTextStyle = TextStyle(fontSize: 14.5);
+    const double horizontalMargin = 3.0;
+    const double columnSpacing = 4.0;
+    const double dividerThickness = 5.0;
+
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: ThemeData(
+          dataTableTheme: DataTableThemeData(
+            dataRowColor: dataRowColor,
+            dataRowHeight: dataRowHeight,
+            dataTextStyle: dataTextStyle,
+            headingRowColor: headingRowColor,
+            headingRowHeight: headingRowHeight,
+            headingTextStyle: headingTextStyle,
+            horizontalMargin: horizontalMargin,
+            columnSpacing: columnSpacing,
+            dividerThickness: dividerThickness,
+          ),
+        ),
+        home: Scaffold(
+          body: DataTable(
+            sortColumnIndex: 0,
+            columns: <DataColumn>[
+              DataColumn(
+                label: const Text('A'),
+                onSort: (int columnIndex, bool ascending) {},
+              ),
+              const DataColumn(label: Text('B')),
+            ],
+            rows: const <DataRow>[
+              DataRow(cells: <DataCell>[
+                DataCell(Text('Data')),
+                DataCell(Text('Data 2')),
+              ]),
+            ],
+          ),
+        ),
+      ),
+    );
+
+    final TextStyle dataRowTextStyle = tester.renderObject<RenderParagraph>(find.text('Data')).text.style;
+    expect(dataRowTextStyle.fontSize, dataTextStyle.fontSize);
+    expect(_tableRowBoxDecoration(tester: tester, index: 1).color, dataRowColor.resolve(<MaterialState>{}));
+    expect(_tableRowBoxDecoration(tester: tester, index: 1).border.top.width, dividerThickness);
+
+    final Finder dataRowContainer = find.ancestor(of: find.text('Data'), matching: find.byType(Container));
+    expect(tester.getSize(dataRowContainer).height, dataRowHeight);
+
+    final TextStyle headingRowTextStyle = tester.renderObject<RenderParagraph>(find.text('A')).text.style;
+    expect(headingRowTextStyle.fontSize, headingTextStyle.fontSize);
+    expect(_tableRowBoxDecoration(tester: tester, index: 0).color, headingRowColor.resolve(<MaterialState>{}));
+
+    final Finder headingRowContainer = find.ancestor(of: find.text('A'), matching: find.byType(Container));
+    expect(tester.getSize(headingRowContainer).height, headingRowHeight);
+    expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin);
+    expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing);
+  });
+
+  testWidgets('DataTable properties are taken over the theme values', (WidgetTester tester) async {
+    final MaterialStateProperty<Color> themeDataRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff0),
+    );
+    const double themeDataRowHeight = 50.0;
+    const TextStyle themeDataTextStyle = TextStyle(fontSize: 11.5);
+    final MaterialStateProperty<Color> themeHeadingRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff1),
+    );
+    const double themeHeadingRowHeight = 51.0;
+    const TextStyle themeHeadingTextStyle = TextStyle(fontSize: 13.5);
+    const double themeHorizontalMargin = 2.0;
+    const double themeColumnSpacing = 3.0;
+    const double themeDividerThickness = 4.0;
+
+    final MaterialStateProperty<Color> dataRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff1),
+    );
+    const double dataRowHeight = 51.0;
+    const TextStyle dataTextStyle = TextStyle(fontSize: 12.5);
+    final MaterialStateProperty<Color> headingRowColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) => const Color(0xfffffff2),
+    );
+    const double headingRowHeight = 52.0;
+    const TextStyle headingTextStyle = TextStyle(fontSize: 14.5);
+    const double horizontalMargin = 3.0;
+    const double columnSpacing = 4.0;
+    const double dividerThickness = 5.0;
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: ThemeData(
+          dataTableTheme: DataTableThemeData(
+            dataRowColor: themeDataRowColor,
+            dataRowHeight: themeDataRowHeight,
+            dataTextStyle: themeDataTextStyle,
+            headingRowColor: themeHeadingRowColor,
+            headingRowHeight: themeHeadingRowHeight,
+            headingTextStyle: themeHeadingTextStyle,
+            horizontalMargin: themeHorizontalMargin,
+            columnSpacing: themeColumnSpacing,
+            dividerThickness: themeDividerThickness,
+          ),
+        ),
+        home: Scaffold(
+          body: DataTable(
+            dataRowColor: dataRowColor,
+            dataRowHeight: dataRowHeight,
+            dataTextStyle: dataTextStyle,
+            headingRowColor: headingRowColor,
+            headingRowHeight: headingRowHeight,
+            headingTextStyle: headingTextStyle,
+            horizontalMargin: horizontalMargin,
+            columnSpacing: columnSpacing,
+            dividerThickness: dividerThickness,
+            sortColumnIndex: 0,
+            columns: <DataColumn>[
+              DataColumn(
+                label: const Text('A'),
+                onSort: (int columnIndex, bool ascending) {},
+              ),
+              const DataColumn(label: Text('B')),
+            ],
+            rows: const <DataRow>[
+              DataRow(cells: <DataCell>[
+                DataCell(Text('Data')),
+                DataCell(Text('Data 2')),
+              ]),
+            ],
+          ),
+        ),
+      ),
+    );
+
+    final TextStyle dataRowTextStyle = tester.renderObject<RenderParagraph>(find.text('Data')).text.style;
+    expect(dataRowTextStyle.fontSize, dataTextStyle.fontSize);
+    expect(_tableRowBoxDecoration(tester: tester, index: 1).color, dataRowColor.resolve(<MaterialState>{}));
+    expect(_tableRowBoxDecoration(tester: tester, index: 1).border.top.width, dividerThickness);
+
+    final Finder dataRowContainer = find.ancestor(of: find.text('Data'), matching: find.byType(Container));
+    expect(tester.getSize(dataRowContainer).height, dataRowHeight);
+
+    final TextStyle headingRowTextStyle = tester.renderObject<RenderParagraph>(find.text('A')).text.style;
+    expect(headingRowTextStyle.fontSize, headingTextStyle.fontSize);
+    expect(_tableRowBoxDecoration(tester: tester, index: 0).color, headingRowColor.resolve(<MaterialState>{}));
+
+    final Finder headingRowContainer = find.ancestor(of: find.text('A'), matching: find.byType(Container));
+    expect(tester.getSize(headingRowContainer).height, headingRowHeight);
+    expect(tester.getTopLeft(find.text('A')).dx, horizontalMargin);
+    expect(tester.getTopLeft(find.text('Data 2')).dx - tester.getTopRight(find.text('Data')).dx, columnSpacing);
+  });
+}
+
+BoxDecoration _tableRowBoxDecoration({WidgetTester tester, int index}) {
+  final Table table = tester.widget(find.byType(Table));
+  final TableRow tableRow = table.children[index];
+  return tableRow.decoration as BoxDecoration;
+}
diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart
index 3ebf2dc..2bc3bf8 100644
--- a/packages/flutter/test/material/theme_data_test.dart
+++ b/packages/flutter/test/material/theme_data_test.dart
@@ -288,6 +288,7 @@
       elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(primary: Colors.green)),
       outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)),
       textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black),
+      dataTableTheme: const DataTableThemeData(),
       fixTextFieldOutlineLabel: false,
       useTextSelectionTheme: false,
     );
@@ -376,6 +377,7 @@
       elevatedButtonTheme: const ElevatedButtonThemeData(),
       outlinedButtonTheme: const OutlinedButtonThemeData(),
       textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white),
+      dataTableTheme: const DataTableThemeData(),
       fixTextFieldOutlineLabel: true,
       useTextSelectionTheme: true,
     );