Added a flag to divide Material slices. (#5402)

This commit changes MergeableMaterial to include a flag that
specifies whether connected Material slices should have dividers
between them.
diff --git a/packages/flutter/lib/src/material/mergeable_material.dart b/packages/flutter/lib/src/material/mergeable_material.dart
index 71ef31e..a66363b 100644
--- a/packages/flutter/lib/src/material/mergeable_material.dart
+++ b/packages/flutter/lib/src/material/mergeable_material.dart
@@ -82,6 +82,7 @@
     Key key,
     this.mainAxis: Axis.vertical,
     this.elevation: 2,
+    this.hasDividers: false,
     this.children: const <MergeableMaterialItem>[]
   }) : super(key: key);
 
@@ -94,6 +95,9 @@
   /// The elevation of all the [Material] slices.
   final int elevation;
 
+  /// Whether connected pieces of [MaterialSlice] have dividers between them.
+  final bool hasDividers;
+
   @override
   String toString() {
     return 'MergeableMaterial('
@@ -234,6 +238,21 @@
     return false;
   }
 
+  void _removeEmptyGaps() {
+    int j = 0;
+
+    while (j < _children.length) {
+      if (
+        _children[j] is MaterialGap &&
+        _animationTuples[_children[j].key].controller.status == AnimationStatus.dismissed
+      ) {
+        _removeChild(j);
+      } else {
+        j += 1;
+      }
+    }
+  }
+
   @override
   void didUpdateConfig(MergeableMaterial oldConfig) {
     super.didUpdateConfig(oldConfig);
@@ -253,17 +272,7 @@
 
     assert(_debugGapsAreValid(newChildren));
 
-    while (j < _children.length) {
-      if (_children[j] is MaterialGap &&
-          _animationTuples[_children[j].key].controller.status
-          == AnimationStatus.dismissed) {
-        _removeChild(j);
-      } else {
-        j += 1;
-      }
-    }
-
-    j = 0;
+    _removeEmptyGaps();
 
     while (i < newChildren.length && j < _children.length) {
       if (newOnly.contains(newChildren[i].key) ||
@@ -471,10 +480,37 @@
     );
   }
 
+  List<Widget> _divideSlices(BuildContext context, List<Widget> slices, List<Key> keys) {
+    if (config.hasDividers) {
+      List<Widget> divided = <Widget>[];
+
+      for (int i = 0; i < slices.length; i += 1) {
+        divided.add(
+          new DecoratedBox(
+            key: keys[i],
+            decoration: i != slices.length - 1 ? new BoxDecoration(
+              border: new Border(
+                bottom: new BorderSide(color: Theme.of(context).dividerColor)
+              )
+            ) : new BoxDecoration(),
+            child: slices[i]
+          )
+        );
+      }
+
+      return divided;
+    } else {
+      return slices;
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
+    _removeEmptyGaps();
+
     final List<Widget> widgets = <Widget>[];
     List<Widget> slices = <Widget>[];
+    List<Key> keys = <Key>[];
     int i;
 
     for (i = 0; i < _children.length; i += 1) {
@@ -489,11 +525,12 @@
             ),
             child: new BlockBody(
               mainAxis: config.mainAxis,
-              children: slices
+              children: _divideSlices(context, slices, keys)
             )
           )
         );
         slices = <Widget>[];
+        keys = <Key>[];
 
         widgets.add(
           new SizedBox(
@@ -506,14 +543,11 @@
 
         slices.add(
           new Material(
-            // Since slices live in different Material widgets, the parent
-            // hierarchy can change and lead to the slice being rebuilt. Using
-            // a global key solves the issue.
-            key: new _MergeableMaterialSliceKey(_children[i].key),
             type: MaterialType.transparency,
             child: slice.child
           )
         );
+        keys.add(new _MergeableMaterialSliceKey(_children[i].key));
       }
     }
 
@@ -527,11 +561,12 @@
           ),
           child: new BlockBody(
             mainAxis: config.mainAxis,
-            children: slices
+            children: _divideSlices(context, slices, keys)
           )
         )
       );
       slices = <Widget>[];
+      keys = <Key>[];
     }
 
     return new _MergeableMaterialBlockBody(
@@ -543,6 +578,8 @@
   }
 }
 
+// The parent hierarchy can change and lead to the slice being
+// rebuilt. Usinga global key solves the issue.
 class _MergeableMaterialSliceKey extends GlobalKey {
   const _MergeableMaterialSliceKey(this.value) : super.constructor();
 
@@ -558,6 +595,11 @@
 
   @override
   int get hashCode => value.hashCode;
+
+  @override
+  String toString() {
+    return '_MergeableMaterialSliceKey($value)';
+  }
 }
 
 class _MergeableMaterialBlockBody extends BlockBody {
diff --git a/packages/flutter/test/material/mergeable_material_test.dart b/packages/flutter/test/material/mergeable_material_test.dart
index 9fd6c72..d031543 100644
--- a/packages/flutter/test/material/mergeable_material_test.dart
+++ b/packages/flutter/test/material/mergeable_material_test.dart
@@ -1014,4 +1014,116 @@
     matches(getBorderRadius(tester, 0), RadiusType.Round, RadiusType.Round);
     matches(getBorderRadius(tester, 1), RadiusType.Round, RadiusType.Round);
   });
+
+  bool isDivider(Widget widget) {
+    final DecoratedBox box = widget;
+
+    return box.decoration == new BoxDecoration(
+      border: new Border(
+        bottom: new BorderSide(color: const Color(0x1F000000))
+      )
+    );
+  }
+
+  testWidgets('MergeableMaterial dividers', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      new Scaffold(
+        body: new ScrollableViewport(
+          child: new MergeableMaterial(
+            hasDividers: true,
+            children: <MergeableMaterialItem>[
+              new MaterialSlice(
+                key: new ValueKey<String>('A'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('B'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('C'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('D'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              )
+            ]
+          )
+        )
+      )
+    );
+
+    List<Widget> boxes = tester.widgetList(find.byType(DecoratedBox)).toList();
+    int offset = 3;
+
+    expect(isDivider(boxes[offset]), isTrue);
+    expect(isDivider(boxes[offset + 1]), isTrue);
+    expect(isDivider(boxes[offset + 2]), isTrue);
+    expect(isDivider(boxes[offset + 3]), isFalse);
+
+    await tester.pumpWidget(
+      new Scaffold(
+        body: new ScrollableViewport(
+          child: new MergeableMaterial(
+            hasDividers: true,
+            children: <MergeableMaterialItem>[
+              new MaterialSlice(
+                key: new ValueKey<String>('A'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('B'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialGap(
+                key: new ValueKey<String>('x')
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('C'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              ),
+              new MaterialSlice(
+                key: new ValueKey<String>('D'),
+                child: new SizedBox(
+                  width: 100.0,
+                  height: 100.0
+                )
+              )
+            ]
+          )
+        )
+      )
+    );
+
+    boxes = tester.widgetList(find.byType(DecoratedBox)).toList();
+    offset = 3;
+
+    expect(isDivider(boxes[offset]), isTrue);
+    expect(isDivider(boxes[offset + 1]), isFalse);
+    // offset + 2 is gap
+    expect(isDivider(boxes[offset + 3]), isTrue);
+    expect(isDivider(boxes[offset + 4]), isFalse);
+  });
 }