diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 3e573e29c..bfb54794c 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -524,7 +524,7 @@ class MonthPicker extends StatefulWidget { _MonthPickerState createState() => new _MonthPickerState(); } -class _MonthPickerState extends State { +class _MonthPickerState extends State with SingleTickerProviderStateMixin { @override void initState() { super.initState(); @@ -533,6 +533,17 @@ class _MonthPickerState extends State { _dayPickerController = new PageController(initialPage: monthPage); _handleMonthPageChanged(monthPage); _updateCurrentDate(); + + // Setup the fade animation for chevrons + _chevronOpacityController = new AnimationController( + duration: const Duration(milliseconds: 500), vsync: this + ); + _chevronOpacityAnimation = new Tween(begin: 1.0, end: 0.5).animate( + new CurvedAnimation( + parent: _chevronOpacityController, + curve: Curves.easeInOut, + ) + ); } @override @@ -559,6 +570,8 @@ class _MonthPickerState extends State { DateTime _currentDisplayedMonthDate; Timer _timer; PageController _dayPickerController; + AnimationController _chevronOpacityController; + Animation _chevronOpacityAnimation; void _updateCurrentDate() { _todayDate = new DateTime.now(); @@ -642,13 +655,25 @@ class _MonthPickerState extends State { children: [ new Semantics( sortKey: _MonthPickerSortKey.calendar, - child: new PageView.builder( - key: new ValueKey(widget.selectedDate), - controller: _dayPickerController, - scrollDirection: Axis.horizontal, - itemCount: _monthDelta(widget.firstDate, widget.lastDate) + 1, - itemBuilder: _buildItems, - onPageChanged: _handleMonthPageChanged, + child: new NotificationListener( + onNotification: (_) { + _chevronOpacityController.forward(); + return false; + }, + child: new NotificationListener( + onNotification: (_) { + _chevronOpacityController.reverse(); + return false; + }, + child: new PageView.builder( + key: new ValueKey(widget.selectedDate), + controller: _dayPickerController, + scrollDirection: Axis.horizontal, + itemCount: _monthDelta(widget.firstDate, widget.lastDate) + 1, + itemBuilder: _buildItems, + onPageChanged: _handleMonthPageChanged, + ), + ), ), ), new PositionedDirectional( @@ -656,10 +681,13 @@ class _MonthPickerState extends State { start: 8.0, child: new Semantics( sortKey: _MonthPickerSortKey.previousMonth, - child: new IconButton( - icon: const Icon(Icons.chevron_left), - tooltip: _isDisplayingFirstMonth ? null : '${localizations.previousMonthTooltip} ${localizations.formatMonthYear(_previousMonthDate)}', - onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth, + child: new FadeTransition( + opacity: _chevronOpacityAnimation, + child: new IconButton( + icon: const Icon(Icons.chevron_left), + tooltip: _isDisplayingFirstMonth ? null : '${localizations.previousMonthTooltip} ${localizations.formatMonthYear(_previousMonthDate)}', + onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth, + ), ), ), ), @@ -668,10 +696,13 @@ class _MonthPickerState extends State { end: 8.0, child: new Semantics( sortKey: _MonthPickerSortKey.nextMonth, - child: new IconButton( - icon: const Icon(Icons.chevron_right), - tooltip: _isDisplayingLastMonth ? null : '${localizations.nextMonthTooltip} ${localizations.formatMonthYear(_nextMonthDate)}', - onPressed: _isDisplayingLastMonth ? null : _handleNextMonth, + child: new FadeTransition( + opacity: _chevronOpacityAnimation, + child: new IconButton( + icon: const Icon(Icons.chevron_right), + tooltip: _isDisplayingLastMonth ? null : '${localizations.nextMonthTooltip} ${localizations.formatMonthYear(_nextMonthDate)}', + onPressed: _isDisplayingLastMonth ? null : _handleNextMonth, + ), ), ), ), diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 871a4b779..4241e9b7f 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -622,4 +622,65 @@ void _tests() { semantics.dispose(); }); + + testWidgets('chervons animate when scrolling month picker', (WidgetTester tester) async { + final Key _datePickerKey = new UniqueKey(); + DateTime _selectedDate = new DateTime(2016, DateTime.july, 26); + + await tester.pumpWidget( + new MaterialApp( + home: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return new Container( + width: 400.0, + child: new SingleChildScrollView( + child: new Material( + child: new MonthPicker( + firstDate: new DateTime(0), + lastDate: new DateTime(9999), + key: _datePickerKey, + selectedDate: _selectedDate, + onChanged: (DateTime value) { + setState(() { + _selectedDate = value; + }); + }, + ), + ), + ), + ); + }, + ), + ) + ); + + final Finder chevronFinder = find.byType(IconButton); + final List chevronRenderers = chevronFinder.evaluate().map( + (Element element) => element.ancestorRenderObjectOfType( + const TypeMatcher())).cast().toList(); + + // Initial chevron animation state should be dismissed + // An AlwaysStoppedAnimation is also found and is ignored + for(RenderAnimatedOpacity renderer in chevronRenderers) { + expect(renderer.opacity.value, equals(1.0)); + expect(renderer.opacity.status, equals(AnimationStatus.dismissed)); + } + + // Drag and hold the picker to test for the opacity change + final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); + await gesture.moveBy(const Offset(50.0, 100.0)); + await tester.pumpAndSettle(); + for(RenderAnimatedOpacity renderer in chevronRenderers) { + expect(renderer.opacity.value, equals(0.5)); + expect(renderer.opacity.status, equals(AnimationStatus.completed)); + } + + // Release the drag and test for the opacity to return to original value + await gesture.up(); + await tester.pumpAndSettle(); + for(RenderAnimatedOpacity renderer in chevronRenderers) { + expect(renderer.opacity.value, equals(1.0)); + expect(renderer.opacity.status, equals(AnimationStatus.dismissed)); + } + }); }