diff --git a/packages/flutter/lib/src/material/mergeable_material.dart b/packages/flutter/lib/src/material/mergeable_material.dart index 71ef31e47d..a66363bb61 100644 --- a/packages/flutter/lib/src/material/mergeable_material.dart +++ b/packages/flutter/lib/src/material/mergeable_material.dart @@ -82,6 +82,7 @@ class MergeableMaterial extends StatefulWidget { Key key, this.mainAxis: Axis.vertical, this.elevation: 2, + this.hasDividers: false, this.children: const [] }) : super(key: key); @@ -94,6 +95,9 @@ class MergeableMaterial extends StatefulWidget { /// 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 @@ class _MergeableMaterialState extends State { 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 @@ class _MergeableMaterialState extends State { 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 @@ class _MergeableMaterialState extends State { ); } + List _divideSlices(BuildContext context, List slices, List keys) { + if (config.hasDividers) { + List divided = []; + + 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 widgets = []; List slices = []; + List keys = []; int i; for (i = 0; i < _children.length; i += 1) { @@ -489,11 +525,12 @@ class _MergeableMaterialState extends State { ), child: new BlockBody( mainAxis: config.mainAxis, - children: slices + children: _divideSlices(context, slices, keys) ) ) ); slices = []; + keys = []; widgets.add( new SizedBox( @@ -506,14 +543,11 @@ class _MergeableMaterialState extends State { 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 @@ class _MergeableMaterialState extends State { ), child: new BlockBody( mainAxis: config.mainAxis, - children: slices + children: _divideSlices(context, slices, keys) ) ) ); slices = []; + keys = []; } return new _MergeableMaterialBlockBody( @@ -543,6 +578,8 @@ class _MergeableMaterialState extends State { } } +// 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 @@ class _MergeableMaterialSliceKey extends GlobalKey { @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 9fd6c72da5..d0315432fd 100644 --- a/packages/flutter/test/material/mergeable_material_test.dart +++ b/packages/flutter/test/material/mergeable_material_test.dart @@ -1014,4 +1014,116 @@ void main() { 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: [ + new MaterialSlice( + key: new ValueKey('A'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialSlice( + key: new ValueKey('B'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialSlice( + key: new ValueKey('C'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialSlice( + key: new ValueKey('D'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ) + ] + ) + ) + ) + ); + + List 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: [ + new MaterialSlice( + key: new ValueKey('A'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialSlice( + key: new ValueKey('B'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialGap( + key: new ValueKey('x') + ), + new MaterialSlice( + key: new ValueKey('C'), + child: new SizedBox( + width: 100.0, + height: 100.0 + ) + ), + new MaterialSlice( + key: new ValueKey('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); + }); }