Expand DropdownButton width based on isExpanded flag (#21515)

* Fix Dropdown where icon not set to end of button

* Expand DropdownButton based on isExpanded flag

* Add dropdown golden tests

Fixes #13135, #16606
diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version
index 4971511..5d80160 100644
--- a/bin/internal/goldens.version
+++ b/bin/internal/goldens.version
@@ -1 +1 @@
-555a2f96848c31b8c0303555535d6b5e20686e6e
+5b0696a651738116b4d25a5b0a946b0951e45e6f
diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index a710bca..043d985 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -484,6 +484,7 @@
     this.style,
     this.iconSize = 24.0,
     this.isDense = false,
+    this.isExpanded = false,
   }) : assert(items != null),
        assert(value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1),
       super(key: key);
@@ -529,6 +530,13 @@
   /// its own decorations, like [InputDecorator].
   final bool isDense;
 
+  /// Set the dropdown's inner contents to horizontally fill its parent.
+  ///
+  /// By default this button's inner width is the minimum size of its contents.
+  /// If [isExpanded] is true, the inner width is expanded to fill its
+  /// surrounding container.
+  final bool isExpanded;
+
   @override
   _DropdownButtonState<T> createState() => new _DropdownButtonState<T>();
 }
@@ -643,6 +651,14 @@
       ? _kAlignedButtonPadding
       : _kUnalignedButtonPadding;
 
+    // If value is null (then _selectedIndex is null) then we display
+    // the hint or nothing at all.
+    final IndexedStack innerItemsWidget = new IndexedStack(
+      index: _selectedIndex ?? hintIndex,
+      alignment: AlignmentDirectional.centerStart,
+      children: items,
+    );
+
     Widget result = new DefaultTextStyle(
       style: _textStyle,
       child: new Container(
@@ -652,13 +668,7 @@
           mainAxisAlignment: MainAxisAlignment.spaceBetween,
           mainAxisSize: MainAxisSize.min,
           children: <Widget>[
-            // If value is null (then _selectedIndex is null) then we display
-            // the hint or nothing at all.
-            new IndexedStack(
-              index: _selectedIndex ?? hintIndex,
-              alignment: AlignmentDirectional.centerStart,
-              children: items,
-            ),
+            widget.isExpanded ? new Expanded(child: innerItemsWidget) : innerItemsWidget,
             new Icon(Icons.arrow_drop_down,
               size: widget.iconSize,
               // These colors are not defined in the Material Design spec.
diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart
index 0693687..118e288 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:io';
 import 'dart:math' as math;
 import 'dart:ui' show window;
 
@@ -23,6 +24,7 @@
   String value = 'two',
   ValueChanged<String> onChanged,
   bool isDense = false,
+  bool isExpanded = false,
   Widget hint,
   List<String> items = menuItems,
   Alignment alignment = Alignment.center,
@@ -33,19 +35,22 @@
     child: new Material(
       child: new Align(
         alignment: alignment,
-        child: new DropdownButton<String>(
-          key: buttonKey,
-          value: value,
-          hint: hint,
-          onChanged: onChanged,
-          isDense: isDense,
-          items: items.map((String item) {
-            return new DropdownMenuItem<String>(
-              key: new ValueKey<String>(item),
-              value: item,
-              child: new Text(item, key: new ValueKey<String>(item + 'Text')),
-            );
-          }).toList(),
+        child: new RepaintBoundary(
+          child: new DropdownButton<String>(
+            key: buttonKey,
+            value: value,
+            hint: hint,
+            onChanged: onChanged,
+            isDense: isDense,
+            isExpanded: isExpanded,
+            items: items.map((String item) {
+              return new DropdownMenuItem<String>(
+                key: new ValueKey<String>(item),
+                value: item,
+                child: new Text(item, key: new ValueKey<String>(item + 'Text')),
+              );
+            }).toList(),
+          )
         ),
       ),
     ),
@@ -108,6 +113,32 @@
 }
 
 void main() {
+  testWidgets('Default dropdown golden', (WidgetTester tester) async {
+    final Key buttonKey = new UniqueKey();
+    Widget build() => buildFrame(buttonKey: buttonKey, value: 'two');
+    await tester.pumpWidget(build());
+    final Finder buttonFinder = find.byKey(buttonKey);
+    assert(tester.renderObject(buttonFinder).attached);
+    await expectLater(
+      find.ancestor(of: buttonFinder, matching: find.byType(RepaintBoundary)).first,
+      matchesGoldenFile('dropdown_test.default.0.png'),
+      skip: !Platform.isLinux,
+    );
+  });
+
+  testWidgets('Expanded dropdown golden', (WidgetTester tester) async {
+    final Key buttonKey = new UniqueKey();
+    Widget build() => buildFrame(buttonKey: buttonKey, value: 'two', isExpanded: true);
+    await tester.pumpWidget(build());
+    final Finder buttonFinder = find.byKey(buttonKey);
+    assert(tester.renderObject(buttonFinder).attached);
+    await expectLater(
+      find.ancestor(of: buttonFinder, matching: find.byType(RepaintBoundary)).first,
+      matchesGoldenFile('dropdown_test.expanded.0.png'),
+      skip: !Platform.isLinux,
+    );
+  });
+
   testWidgets('Dropdown button control test', (WidgetTester tester) async {
     String value = 'one';
     void didChangeValue(String newValue) {
@@ -337,6 +368,23 @@
     });
   }
 
+  testWidgets('Arrow icon aligns with the edge of button when expanded', (WidgetTester tester) async {
+    final Key buttonKey = new UniqueKey();
+
+    Widget build() => buildFrame(buttonKey: buttonKey, value: 'two', isExpanded: true);
+
+    await tester.pumpWidget(build());
+    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
+    assert(buttonBox.attached);
+
+    final RenderBox arrowIcon = tester.renderObject(find.byIcon(Icons.arrow_drop_down));
+    assert(arrowIcon.attached);
+
+    // Arrow icon should be aligned with far right of button when expanded
+    expect(arrowIcon.localToGlobal(Offset.zero).dx,
+        buttonBox.size.centerRight(new Offset(-arrowIcon.size.width, 0.0)).dx);
+  });
+
   testWidgets('Dropdown button with isDense:true aligns selected menu item', (WidgetTester tester) async {
     final Key buttonKey = new UniqueKey();
     const String value = 'two';