From 263de65f4899a8bf667dc14bbffb929938a261a9 Mon Sep 17 00:00:00 2001 From: Ayush Bherwani Date: Wed, 5 Aug 2020 21:56:04 +0530 Subject: [PATCH] [ExpansionPanelList] adds elevation property (#62840) --- .../lib/src/material/expansion_panel.dart | 20 ++++++ .../flutter/lib/src/material/shadows.dart | 3 + .../test/material/expansion_panel_test.dart | 64 +++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/packages/flutter/lib/src/material/expansion_panel.dart b/packages/flutter/lib/src/material/expansion_panel.dart index 17c663a4b6..ec7ec5e79d 100644 --- a/packages/flutter/lib/src/material/expansion_panel.dart +++ b/packages/flutter/lib/src/material/expansion_panel.dart @@ -12,6 +12,7 @@ import 'expand_icon.dart'; import 'ink_well.dart'; import 'material_localizations.dart'; import 'mergeable_material.dart'; +import 'shadows.dart'; import 'theme.dart'; const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension; @@ -231,6 +232,7 @@ class ExpansionPanelList extends StatefulWidget { this.animationDuration = kThemeAnimationDuration, this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding, this.dividerColor, + this.elevation = 2, }) : assert(children != null), assert(animationDuration != null), _allowOnlyOnePanelOpen = false, @@ -321,6 +323,7 @@ class ExpansionPanelList extends StatefulWidget { this.initialOpenPanelValue, this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding, this.dividerColor, + this.elevation = 2, }) : assert(children != null), assert(animationDuration != null), _allowOnlyOnePanelOpen = true, @@ -371,6 +374,17 @@ class ExpansionPanelList extends StatefulWidget { /// is null, then [ThemeData.dividerColor] is used. final Color dividerColor; + /// Defines elevation for the [ExpansionPanel] while it's expanded. + /// + /// This uses [kElevationToShadow] to simulate shadows, it does not use + /// [Material]'s arbitrary elevation feature. + /// + /// The following values can be used to define the elevation: 0, 1, 2, 3, 4, 6, + /// 8, 9, 12, 16, 24. + /// + /// By default, the value of elevation is 2. + final int elevation; + @override State createState() => _ExpansionPanelListState(); } @@ -456,6 +470,11 @@ class _ExpansionPanelListState extends State { @override Widget build(BuildContext context) { + assert(kElevationToShadow.containsKey(widget.elevation), + 'Invalid value for elevation. See the kElevationToShadow constant for' + ' possible elevation values.' + ); + final List items = []; for (int index = 0; index < widget.children.length; index += 1) { @@ -537,6 +556,7 @@ class _ExpansionPanelListState extends State { return MergeableMaterial( hasDividers: true, dividerColor: widget.dividerColor, + elevation: widget.elevation, children: items, ); } diff --git a/packages/flutter/lib/src/material/shadows.dart b/packages/flutter/lib/src/material/shadows.dart index 8570da76e3..bce61e63e7 100644 --- a/packages/flutter/lib/src/material/shadows.dart +++ b/packages/flutter/lib/src/material/shadows.dart @@ -33,6 +33,9 @@ const Color _kKeyUmbraOpacity = Color(0x33000000); // alpha = 0.2 const Color _kKeyPenumbraOpacity = Color(0x24000000); // alpha = 0.14 const Color _kAmbientShadowOpacity = Color(0x1F000000); // alpha = 0.12 const Map> _elevationToShadow = >{ + // The empty list depicts no elevation. + 0: [], + 1: [ BoxShadow(offset: Offset(0.0, 2.0), blurRadius: 1.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity), BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 1.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity), diff --git a/packages/flutter/test/material/expansion_panel_test.dart b/packages/flutter/test/material/expansion_panel_test.dart index 5fc8e060d3..a6d13a769a 100644 --- a/packages/flutter/test/material/expansion_panel_test.dart +++ b/packages/flutter/test/material/expansion_panel_test.dart @@ -15,12 +15,14 @@ class SimpleExpansionPanelListTestWidget extends StatefulWidget { this.canTapOnHeader = false, this.expandedHeaderPadding, this.dividerColor, + this.elevation = 2, }) : super(key: key); final Key firstPanelKey; final Key secondPanelKey; final bool canTapOnHeader; final Color dividerColor; + final int elevation; /// If null, the default [ExpansionPanelList]'s expanded header padding value is applied via [defaultExpandedHeaderPadding] final EdgeInsets expandedHeaderPadding; @@ -48,6 +50,7 @@ class _SimpleExpansionPanelListTestWidgetState extends State[ ExpansionPanel( headerBuilder: (BuildContext context, bool isExpanded) { @@ -1396,4 +1399,65 @@ void main() { // For the last DecoratedBox, we will have a Border.top with the provided dividerColor. expect(boxDecoration.border.top.color, dividerColor); }); + + testWidgets('elevation is propagated properly to MergeableMaterial', (WidgetTester tester) async { + const int _elevation = 8; + + // Test for ExpansionPanelList. + await tester.pumpWidget(const MaterialApp( + home: SingleChildScrollView( + child: SimpleExpansionPanelListTestWidget( + elevation: _elevation, + ), + ), + )); + + expect(tester.widget(find.byType(MergeableMaterial)).elevation, _elevation); + + // Test for ExpansionPanelList.radio. + await tester.pumpWidget(MaterialApp( + home: SingleChildScrollView( + child: ExpansionPanelList.radio( + elevation: _elevation, + children: [ + ExpansionPanelRadio( + headerBuilder: (BuildContext context, bool isExpanded) { + return Text(isExpanded ? 'B' : 'A', key: const Key('firstKey')); + }, + body: const SizedBox(height: 100.0), + value: 0, + ), + ExpansionPanelRadio( + headerBuilder: (BuildContext context, bool isExpanded) { + return Text(isExpanded ? 'D' : 'C', key: const Key('secondKey')); + }, + body: const SizedBox(height: 100.0), + value: 1, + ), + ], + ), + ), + )); + + expect(tester.widget(find.byType(MergeableMaterial)).elevation, _elevation); + }); + + testWidgets('Using a value non defined value throws assertion error', (WidgetTester tester) async { + + // It should throw an AssertionError since, 19 is not defined in kElevationToShadow. + await tester.pumpWidget(const MaterialApp( + home: SingleChildScrollView( + child: SimpleExpansionPanelListTestWidget( + elevation: 19, + ), + ), + )); + + final dynamic exception = tester.takeException(); + expect(exception, isAssertionError); + expect((exception as AssertionError).toString(), contains( + 'Invalid value for elevation. See the kElevationToShadow constant for' + ' possible elevation values.' + )); + }); }