DropdownButton Icon customizability (#29572)

diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index 092125e..5afc7e3 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -599,6 +599,9 @@
     @required this.onChanged,
     this.elevation = 8,
     this.style,
+    this.icon,
+    this.iconDisabledColor,
+    this.iconEnabledColor,
     this.iconSize = 24.0,
     this.isDense = false,
     this.isExpanded = false,
@@ -653,6 +656,27 @@
   /// [ThemeData.textTheme] of the current [Theme].
   final TextStyle style;
 
+  /// The widget to use for the drop-down button's icon.
+  ///
+  /// Defaults to an [Icon] with the [Icons.arrow_drop_down] glyph.
+  final Widget icon;
+
+  /// The color of any [Icon] descendant of [icon] if this button is disabled,
+  /// i.e. if [onChanged] is null.
+  ///
+  /// Defaults to [Colors.grey.shade400] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white10] when it is [Brightness.dark]
+  final Color iconDisabledColor;
+
+  /// The color of any [Icon] descendant of [icon] if this button is enabled,
+  /// i.e. if [onChanged] is defined.
+  ///
+  /// Defaults to [Colors.grey.shade700] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white70] when it is [Brightness.dark]
+  final Color iconEnabledColor;
+
   /// The size to use for the drop-down button's down arrow icon button.
   ///
   /// Defaults to 24.0.
@@ -768,21 +792,34 @@
     return math.max(_textStyle.fontSize, math.max(widget.iconSize, _kDenseButtonHeight));
   }
 
-  Color get _downArrowColor {
+  Color get _iconColor {
     // These colors are not defined in the Material Design spec.
     if (_enabled) {
-      if (Theme.of(context).brightness == Brightness.light) {
-        return Colors.grey.shade700;
-      } else {
-        return Colors.white70;
+      if (widget.iconEnabledColor != null) {
+        return widget.iconEnabledColor;
+      }
+
+      switch(Theme.of(context).brightness) {
+        case Brightness.light:
+          return Colors.grey.shade700;
+        case Brightness.dark:
+          return Colors.white70;
       }
     } else {
-      if (Theme.of(context).brightness == Brightness.light) {
-        return Colors.grey.shade400;
-      } else {
-        return Colors.white10;
+      if (widget.iconDisabledColor != null) {
+        return widget.iconDisabledColor;
+      }
+
+      switch(Theme.of(context).brightness) {
+        case Brightness.light:
+          return Colors.grey.shade400;
+        case Brightness.dark:
+          return Colors.white10;
       }
     }
+
+    assert(false);
+    return null;
   }
 
   bool get _enabled => widget.items != null && widget.items.isNotEmpty && widget.onChanged != null;
@@ -827,6 +864,8 @@
       );
     }
 
+    const Icon defaultIcon = Icon(Icons.arrow_drop_down);
+
     Widget result = DefaultTextStyle(
       style: _textStyle,
       child: Container(
@@ -837,9 +876,12 @@
           mainAxisSize: MainAxisSize.min,
           children: <Widget>[
             widget.isExpanded ? Expanded(child: innerItemsWidget) : innerItemsWidget,
-            Icon(Icons.arrow_drop_down,
-              size: widget.iconSize,
-              color: _downArrowColor,
+            IconTheme(
+              data: IconThemeData(
+                color: _iconColor,
+                size: widget.iconSize,
+              ),
+              child: widget.icon ?? defaultIcon,
             ),
           ],
         ),
diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart
index 14c0075..b70fd24 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -20,10 +20,21 @@
   items: const <DropdownMenuItem<String>>[],
 ).runtimeType;
 
+Finder _iconRichText(Key iconKey) {
+  return find.descendant(
+    of: find.byKey(iconKey),
+    matching: find.byType(RichText),
+  );
+}
+
 Widget buildFrame({
   Key buttonKey,
   String value = 'two',
   ValueChanged<String> onChanged,
+  Widget icon,
+  Color iconDisabledColor,
+  Color iconEnabledColor,
+  double iconSize = 24.0,
   bool isDense = false,
   bool isExpanded = false,
   Widget hint,
@@ -44,6 +55,10 @@
             hint: hint,
             disabledHint: disabledHint,
             onChanged: onChanged,
+            icon: icon,
+            iconSize: iconSize,
+            iconDisabledColor: iconDisabledColor,
+            iconEnabledColor: iconEnabledColor,
             isDense: isDense,
             isExpanded: isExpanded,
             items: items == null ? null : items.map<DropdownMenuItem<String>>((String item) {
@@ -430,6 +445,134 @@
         buttonBox.size.centerRight(Offset(-arrowIcon.size.width, 0.0)).dx);
   });
 
+  testWidgets('Dropdown button icon will accept widgets as icons', (WidgetTester tester) async {
+    final Widget customWidget = Container(
+      decoration: ShapeDecoration(
+        shape: CircleBorder(
+          side: BorderSide(
+            width: 5.0,
+            color: Colors.grey.shade700,
+          ),
+        ),
+      ),
+    );
+
+    await tester.pumpWidget(buildFrame(
+      icon: customWidget,
+      onChanged: onChanged,
+    ));
+
+    expect(find.byWidget(customWidget), findsOneWidget);
+    expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
+
+    await tester.pumpWidget(buildFrame(
+      icon: const Icon(Icons.assessment),
+      onChanged: onChanged,
+    ));
+
+    expect(find.byIcon(Icons.assessment), findsOneWidget);
+    expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
+  });
+
+  testWidgets('Dropdown button icon should have default size and colors when not defined', (WidgetTester tester) async {
+    final Key iconKey = UniqueKey();
+    final Icon customIcon = Icon(Icons.assessment, key: iconKey);
+
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      onChanged: onChanged,
+    ));
+
+    // test for size
+    final RenderBox icon = tester.renderObject(find.byKey(iconKey));
+    expect(icon.size, const Size(24.0, 24.0));
+
+    // test for enabled color
+    final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(enabledRichText.text.style.color, Colors.grey.shade700);
+
+    // test for disabled color
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      onChanged: null,
+    ));
+
+    final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(disabledRichText.text.style.color, Colors.grey.shade400);
+  });
+
+  testWidgets('Dropdown button icon should have the passed in size and color instead of defaults', (WidgetTester tester) async {
+    final Key iconKey = UniqueKey();
+    final Icon customIcon = Icon(Icons.assessment, key: iconKey);
+
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      iconSize: 30.0,
+      iconEnabledColor: Colors.pink,
+      iconDisabledColor: Colors.orange,
+      onChanged: onChanged,
+    ));
+
+    // test for size
+    final RenderBox icon = tester.renderObject(find.byKey(iconKey));
+    expect(icon.size, const Size(30.0, 30.0));
+
+    // test for enabled color
+    final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(enabledRichText.text.style.color, Colors.pink);
+
+    // test for disabled color
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      iconSize: 30.0,
+      iconEnabledColor: Colors.pink,
+      iconDisabledColor: Colors.orange,
+      onChanged: null,
+    ));
+
+    final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(disabledRichText.text.style.color, Colors.orange);
+  });
+
+  testWidgets('Dropdown button should use its own size and color properties over those defined by the theme', (WidgetTester tester) async {
+    final Key iconKey = UniqueKey();
+
+    final Icon customIcon = Icon(
+      Icons.assessment,
+      key: iconKey,
+      size: 40.0,
+      color: Colors.yellow,
+    );
+
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      iconSize: 30.0,
+      iconEnabledColor: Colors.pink,
+      iconDisabledColor: Colors.orange,
+      onChanged: onChanged,
+    ));
+
+    // test for size
+    final RenderBox icon = tester.renderObject(find.byKey(iconKey));
+    expect(icon.size, const Size(40.0, 40.0));
+
+    // test for enabled color
+    final RichText enabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(enabledRichText.text.style.color, Colors.yellow);
+
+    // test for disabled color
+    await tester.pumpWidget(buildFrame(
+      icon: customIcon,
+      iconSize: 30.0,
+      iconEnabledColor: Colors.pink,
+      iconDisabledColor: Colors.orange,
+      onChanged: null,
+    ));
+
+    final RichText disabledRichText = tester.widget<RichText>(_iconRichText(iconKey));
+    expect(disabledRichText.text.style.color, Colors.yellow);
+  });
+
   testWidgets('Dropdown button with isDense:true aligns selected menu item', (WidgetTester tester) async {
     final Key buttonKey = UniqueKey();
     const String value = 'two';