Add FlexFit.loose (#5908)

Previously, flexible children were always required to fill their allocated
space. After this change, they can fit loosely into that space and not fill it.
When that happens, the remaining free space is allocated according to the
maixAxisAlignment.

Fixes #5858
diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart
index 93b6c43..91ee5a8 100644
--- a/packages/flutter/lib/src/rendering/flex.dart
+++ b/packages/flutter/lib/src/rendering/flex.dart
@@ -7,16 +7,35 @@
 import 'box.dart';
 import 'object.dart';
 
+/// How the child is inscribed into the available space.
+enum FlexFit {
+  /// The child is forced to fill the available space.
+  tight,
+
+  /// The child can be at most as large as the available space (but is
+  /// allowed to be smaller).
+  loose,
+}
+
 /// Parent data for use with [RenderFlex].
 class FlexParentData extends ContainerBoxParentDataMixin<RenderBox> {
   /// The flex factor to use for this child
   ///
-  /// If null, the child is inflexible and determines its own size. If non-null,
-  /// the child is flexible and its extent in the main axis is determined by
-  /// dividing the free space (after placing the inflexible children)
-  /// according to the flex factors of the flexible children.
+  /// If null or zero, the child is inflexible and determines its own size. If
+  /// non-zero, the amount of space the child's can occupy in the main axis is
+  /// determined by dividing the free space (after placing the inflexible
+  /// children) according to the flex factors of the flexible children.
   int flex;
 
+  /// How a flexible child is inscribed into the available space.
+  ///
+  /// If [flex] is non-zero, the [fit] determines whether the child fills the
+  /// space the parent makes available during layout. If the fit is
+  /// [FlexFit.tight], the child is required to fill the available space. If the
+  /// fit is [FlexFit.loose], the child can be at most as large as the available
+  /// space (but is allowed to be smaller).
+  FlexFit fit;
+
   @override
   String toString() => '${super.toString()}; flex=$flex';
 }
@@ -85,7 +104,8 @@
 /// children. Otherwise, the flex expands to the maximum max-axis size and the
 /// remaining space along is divided among the flexible children according to
 /// their flex factors. Any remaining free space (i.e., if there aren't any
-/// flexible children) is allocated according to the [mainAxisAlignment] property.
+/// flexible children or some of the flexible children have a loose fit) is
+/// allocated according to the [mainAxisAlignment] property.
 ///
 /// In the cross axis, children determine their own size. The flex then sizes
 /// its cross axis to fix the largest of its children. The children are then
@@ -297,7 +317,12 @@
 
   int _getFlex(RenderBox child) {
     final FlexParentData childParentData = child.parentData;
-    return childParentData.flex != null ? childParentData.flex : 0;
+    return childParentData.flex ?? 0;
+  }
+
+  FlexFit _getFit(RenderBox child) {
+    final FlexParentData childParentData = child.parentData;
+    return childParentData.fit ?? FlexFit.tight;
   }
 
   double _getCrossSize(RenderBox child) {
@@ -326,7 +351,7 @@
     while (child != null) {
       final FlexParentData childParentData = child.parentData;
       totalChildren++;
-      int flex = _getFlex(child);
+      final int flex = _getFlex(child);
       if (flex > 0) {
         assert(() {
           final String identity = _direction == Axis.horizontal ? 'row' : 'column';
@@ -420,43 +445,53 @@
     _overflow = math.max(0.0, allocatedSize - (canFlex ? maxMainSize : 0.0));
 
     // Distribute free space to flexible children, and determine baseline.
-    double freeSpace = math.max(0.0, (canFlex ? availableSize : 0.0) - allocatedSize);
+    final double freeSpace = math.max(0.0, (canFlex ? availableSize : 0.0) - allocatedSize);
     double maxBaselineDistance = 0.0;
     double usedSpace = 0.0;
     if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
-      double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
+      final double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
       child = firstChild;
       while (child != null) {
-        int flex = _getFlex(child);
+        final int flex = _getFlex(child);
         if (flex > 0) {
-          double spaceForChild = spacePerFlex * flex;
+          final double maxChildExtent = spacePerFlex * flex;
+          double minChildExtent;
+          switch (_getFit(child)) {
+            case FlexFit.tight:
+              minChildExtent = maxChildExtent;
+              break;
+            case FlexFit.loose:
+              minChildExtent = 0.0;
+              break;
+          }
+          assert(minChildExtent != null);
           BoxConstraints innerConstraints;
           if (crossAxisAlignment == CrossAxisAlignment.stretch) {
             switch (_direction) {
               case Axis.horizontal:
-                innerConstraints = new BoxConstraints(minWidth: spaceForChild,
-                                                      maxWidth: spaceForChild,
+                innerConstraints = new BoxConstraints(minWidth: minChildExtent,
+                                                      maxWidth: maxChildExtent,
                                                       minHeight: constraints.maxHeight,
                                                       maxHeight: constraints.maxHeight);
                 break;
               case Axis.vertical:
                 innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
                                                       maxWidth: constraints.maxWidth,
-                                                      minHeight: spaceForChild,
-                                                      maxHeight: spaceForChild);
+                                                      minHeight: minChildExtent,
+                                                      maxHeight: maxChildExtent);
                 break;
             }
           } else {
             switch (_direction) {
               case Axis.horizontal:
-                innerConstraints = new BoxConstraints(minWidth: spaceForChild,
-                                                      maxWidth: spaceForChild,
+                innerConstraints = new BoxConstraints(minWidth: minChildExtent,
+                                                      maxWidth: maxChildExtent,
                                                       maxHeight: constraints.maxHeight);
                 break;
               case Axis.vertical:
                 innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
-                                                      minHeight: spaceForChild,
-                                                      maxHeight: spaceForChild);
+                                                      minHeight: minChildExtent,
+                                                      maxHeight: maxChildExtent);
                 break;
             }
           }
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 52725fb..d76e305 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -20,7 +20,7 @@
     CustomClipper,
     CustomPainter,
     FixedColumnCountGridDelegate,
-    Axis,
+    FlexFit,
     FlowDelegate,
     FlowPaintingContext,
     FractionalOffsetTween,
@@ -1949,23 +1949,44 @@
   Flexible({
     Key key,
     this.flex: 1,
+    this.fit: FlexFit.tight,
     @required Widget child
   }) : super(key: key, child: child);
 
   /// The flex factor to use for this child
   ///
-  /// If null, the child is inflexible and determines its own size. If non-null,
-  /// the child is flexible and its extent in the main axis is determined by
-  /// dividing the free space (after placing the inflexible children)
-  /// according to the flex factors of the flexible children.
+  /// If null or zero, the child is inflexible and determines its own size. If
+  /// non-zero, the amount of space the child's can occupy in the main axis is
+  /// determined by dividing the free space (after placing the inflexible
+  /// children) according to the flex factors of the flexible children.
   final int flex;
 
+  /// How a flexible child is inscribed into the available space.
+  ///
+  /// If [flex] is non-zero, the [fit] determines whether the child fills the
+  /// space the parent makes available during layout. If the fit is
+  /// [FlexFit.tight], the child is required to fill the available space. If the
+  /// fit is [FlexFit.loose], the child can be at most as large as the available
+  /// space (but is allowed to be smaller).
+  final FlexFit fit;
+
   @override
   void applyParentData(RenderObject renderObject) {
     assert(renderObject.parentData is FlexParentData);
     final FlexParentData parentData = renderObject.parentData;
+    bool needsLayout = false;
+
     if (parentData.flex != flex) {
       parentData.flex = flex;
+      needsLayout = true;
+    }
+
+    if (parentData.fit != fit) {
+      parentData.fit = fit;
+      needsLayout = true;
+    }
+
+    if (needsLayout) {
       AbstractNode targetParent = renderObject.parent;
       if (targetParent is RenderObject)
         targetParent.markNeedsLayout();
diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart
index 99bd6b3..5794578 100644
--- a/packages/flutter/test/rendering/flex_test.dart
+++ b/packages/flutter/test/rendering/flex_test.dart
@@ -166,4 +166,51 @@
     expect(getOffset(box3).dy, equals(275.0));
     expect(box3.size.height, equals(100.0));
   });
+
+  test('Fit.loose', () {
+    RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
+    RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
+    RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: new BoxConstraints.tightFor(width: 100.0, height: 100.0));
+    RenderFlex flex = new RenderFlex(mainAxisAlignment: MainAxisAlignment.spaceBetween);
+    flex.addAll(<RenderBox>[box1, box2, box3]);
+    layout(flex, constraints: const BoxConstraints(
+      minWidth: 0.0, maxWidth: 500.0, minHeight: 0.0, maxHeight: 400.0)
+    );
+    Offset getOffset(RenderBox box) {
+      FlexParentData parentData = box.parentData;
+      return parentData.offset;
+    }
+    expect(getOffset(box1).dx, equals(0.0));
+    expect(box1.size.width, equals(100.0));
+    expect(getOffset(box2).dx, equals(200.0));
+    expect(box2.size.width, equals(100.0));
+    expect(getOffset(box3).dx, equals(400.0));
+    expect(box3.size.width, equals(100.0));
+
+    void setFit(RenderBox box, FlexFit fit) {
+      FlexParentData parentData = box.parentData;
+      parentData.flex = 1;
+      parentData.fit = fit;
+    }
+
+    setFit(box1, FlexFit.loose);
+
+    pumpFrame();
+    expect(getOffset(box1).dx, equals(0.0));
+    expect(box1.size.width, equals(100.0));
+    expect(getOffset(box2).dx, equals(200.0));
+    expect(box2.size.width, equals(100.0));
+    expect(getOffset(box3).dx, equals(400.0));
+    expect(box3.size.width, equals(100.0));
+
+    box1.additionalConstraints = new BoxConstraints.tightFor(width: 1000.0, height: 100.0);
+
+    pumpFrame();
+    expect(getOffset(box1).dx, equals(0.0));
+    expect(box1.size.width, equals(300.0));
+    expect(getOffset(box2).dx, equals(300.0));
+    expect(box2.size.width, equals(100.0));
+    expect(getOffset(box3).dx, equals(400.0));
+    expect(box3.size.width, equals(100.0));
+  });
 }