diff --git a/examples/flutter_gallery/lib/demo/animation/home.dart b/examples/flutter_gallery/lib/demo/animation/home.dart index 7af468e2780..698704692a8 100644 --- a/examples/flutter_gallery/lib/demo/animation/home.dart +++ b/examples/flutter_gallery/lib/demo/animation/home.dart @@ -15,6 +15,8 @@ import 'sections.dart'; import 'widgets.dart'; const Color _kAppBackgroundColor = const Color(0xFF353662); +const Duration _kScrollDuration = const Duration(milliseconds: 400); +const Curve _kScrollCurve = Curves.fastOutSlowIn; // This app's contents start out at _kHeadingMaxHeight and they function like // an appbar. Initially the appbar occupies most of the screen and its section @@ -449,6 +451,13 @@ class _AnimationDemoHomeState extends State { ); } + void _handleBackButton(double midScrollOffset) { + if (_scrollController.offset >= midScrollOffset) + _scrollController.animateTo(0.0, curve: _kScrollCurve, duration: _kScrollDuration); + else + Navigator.of(context).maybePop(); + } + // Only enable paging for the heading when the user has scrolled to midScrollOffset. // Paging is enabled/disabled by setting the heading's PageView scroll physics. bool _handleScrollNotification(ScrollNotification notification, double midScrollOffset) { @@ -466,18 +475,16 @@ class _AnimationDemoHomeState extends State { } void _maybeScroll(double midScrollOffset, int pageIndex, double xOffset) { - const Duration duration = const Duration(milliseconds: 400); - const Curve curve = Curves.fastOutSlowIn; if (_scrollController.offset < midScrollOffset) { // Scroll the overall list to the point where only one section card shows. // At the same time scroll the PageViews to the page at pageIndex. - _headingPageController.animateToPage(pageIndex, curve: curve, duration: duration); - _scrollController.animateTo(midScrollOffset, curve: curve, duration: duration); + _headingPageController.animateToPage(pageIndex, curve: _kScrollCurve, duration: _kScrollDuration); + _scrollController.animateTo(midScrollOffset, curve: _kScrollCurve, duration: _kScrollDuration); } else { // One one section card is showing: scroll one page forward or back. final double centerX = _headingPageController.position.viewportDimension / 2.0; final int newPageIndex = xOffset > centerX ? pageIndex + 1 : pageIndex - 1; - _headingPageController.animateToPage(newPageIndex, curve: curve, duration: duration); + _headingPageController.animateToPage(newPageIndex, curve: _kScrollCurve, duration: _kScrollDuration); } } @@ -605,9 +612,15 @@ class _AnimationDemoHomeState extends State { new Positioned( top: statusBarHeight, left: 0.0, - child: const IconTheme( + child: new IconTheme( data: const IconThemeData(color: Colors.white), - child: const BackButton(), + child: new IconButton( + icon: const BackButtonIcon(), + tooltip: 'Back', + onPressed: () { + _handleBackButton(appBarMidScrollOffset); + } + ), ), ), ], diff --git a/packages/flutter/lib/src/material/back_button.dart b/packages/flutter/lib/src/material/back_button.dart index 7fc4c227e42..f2f24627298 100644 --- a/packages/flutter/lib/src/material/back_button.dart +++ b/packages/flutter/lib/src/material/back_button.dart @@ -9,6 +9,35 @@ import 'icon_button.dart'; import 'icons.dart'; import 'theme.dart'; +/// A "back" icon that's appropriate for the current [TargetPlatform]. +/// +/// See also: +/// +/// * [BackButton], an [IconButton] with a [BackButtonIcon] that calls +/// [Navigator.maybePop] to return to the previous route. +/// * [IconButton], which is a more general widget for creating buttons +/// with icons. +/// * [Icon], a material design icon. +class BackButtonIcon extends StatelessWidget { + const BackButtonIcon({ Key key }) : super(key: key); + + /// Returns tha appropriate "back" icon for the given `platform`. + static IconData _getIconData(TargetPlatform platform) { + switch (platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + return Icons.arrow_back; + case TargetPlatform.iOS: + return Icons.arrow_back_ios; + } + assert(false); + return null; + } + + @override + Widget build(BuildContext context) => new Icon(_getIconData(Theme.of(context).platform)); +} + /// A material design back button. /// /// A [BackButton] is an [IconButton] with a "back" icon appropriate for the @@ -27,6 +56,8 @@ import 'theme.dart'; /// /// * [AppBar], which automatically uses a [BackButton] in its /// [AppBar.leading] slot when appropriate. +/// * [BackButtonIcon], which is useful if you need to create a back button +/// that responds differently to being pressed. /// * [IconButton], which is a more general widget for creating buttons with /// icons. /// * [CloseButton], an alternative which may be more appropriate for leaf @@ -36,27 +67,14 @@ class BackButton extends StatelessWidget { /// target platform. const BackButton({ Key key }) : super(key: key); - /// Returns tha appropriate "back" icon for the given `platform`. - static IconData getIconData(TargetPlatform platform) { - switch (platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - return Icons.arrow_back; - case TargetPlatform.iOS: - return Icons.arrow_back_ios; - } - assert(false); - return null; - } - @override Widget build(BuildContext context) { return new IconButton( - icon: new Icon(getIconData(Theme.of(context).platform)), + icon: const BackButtonIcon(), tooltip: 'Back', // TODO(ianh): Figure out how to localize this string onPressed: () { Navigator.of(context).maybePop(); - }, + } ); } } diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart index f1e8c1a02c9..cc168f581f0 100644 --- a/packages/flutter/test/material/back_button_test.dart +++ b/packages/flutter/test/material/back_button_test.dart @@ -32,4 +32,31 @@ void main() { expect(find.text('Home'), findsOneWidget); }); + + testWidgets('BackButton icon', (WidgetTester tester) async { + final Key iOSKey = new UniqueKey(); + final Key androidKey = new UniqueKey(); + + + await tester.pumpWidget( + new MaterialApp( + home: new Column( + children: [ + new Theme( + data: new ThemeData(platform: TargetPlatform.iOS), + child: new BackButtonIcon(key: iOSKey), + ), + new Theme( + data: new ThemeData(platform: TargetPlatform.android), + child: new BackButtonIcon(key: androidKey), + ), + ], + ), + ), + ); + + final Icon iOSIcon = tester.widget(find.descendant(of: find.byKey(iOSKey), matching: find.byType(Icon))); + final Icon androidIcon = tester.widget(find.descendant(of: find.byKey(androidKey), matching: find.byType(Icon))); + expect(iOSIcon == androidIcon, false); + }); }