From b397406561f5e7a9c94e28f58d9e49fca0dd58b7 Mon Sep 17 00:00:00 2001 From: xster Date: Mon, 2 Apr 2018 13:53:20 -0700 Subject: [PATCH] Let CupertinoTabScaffold's tab be programmatically selectable (#16040) * Let CupertinoTabScaffold's tab be programmatically selectable * Re-use the tab bar's index instead * review --- .../lib/src/cupertino/bottom_tab_bar.dart | 7 +++ .../lib/src/cupertino/tab_scaffold.dart | 35 +++++++++-- .../test/cupertino/tab_scaffold_test.dart | 58 ++++++++++++++++++- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index 9eec8032e6..cf12016a38 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -47,11 +47,14 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { this.iconSize: 30.0, }) : assert(items != null), assert(items.length >= 2), + assert(currentIndex != null), assert(0 <= currentIndex && currentIndex < items.length), assert(iconSize != null), super(key: key); /// The interactive items laid out within the bottom navigation bar. + /// + /// Must not be null. final List items; /// The callback that is called when a item is tapped. @@ -62,6 +65,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { final ValueChanged onTap; /// The index into [items] of the current active item. + /// + /// Must not be null. final int currentIndex; /// The background color of the tab bar. If it contains transparency, the @@ -82,6 +87,8 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget { /// This value is used to to configure the [IconTheme] for the navigation /// bar. When a [BottomNavigationBarItem.icon] widget is not an [Icon] the widget /// should configure itself to match the icon theme's size and color. + /// + /// Must not be null. final double iconSize; /// True if the tab bar's background color has no transparency. diff --git a/packages/flutter/lib/src/cupertino/tab_scaffold.dart b/packages/flutter/lib/src/cupertino/tab_scaffold.dart index 3b7bb6060f..3238108417 100644 --- a/packages/flutter/lib/src/cupertino/tab_scaffold.dart +++ b/packages/flutter/lib/src/cupertino/tab_scaffold.dart @@ -83,7 +83,10 @@ import 'bottom_tab_bar.dart'; class CupertinoTabScaffold extends StatefulWidget { /// Creates a layout for applications with a tab bar at the bottom. /// - /// The [tabBar] and [tabBuilder] arguments must not be null. + /// The [tabBar], [tabBuilder] and [currentTabIndex] arguments must not be null. + /// + /// The [currentTabIndex] argument can be used to programmatically change the + /// currently selected tab. const CupertinoTabScaffold({ Key key, @required this.tabBar, @@ -96,16 +99,20 @@ class CupertinoTabScaffold extends StatefulWidget { /// that lets the user switch between different tabs in the main content area /// when present. /// - /// When provided, [CupertinoTabBar.currentIndex] will be ignored and will - /// be managed by the [CupertinoTabScaffold] to show the currently selected page - /// as the active item index. If [CupertinoTabBar.onTap] is provided, it will - /// still be called. [CupertinoTabScaffold] automatically also listen to the + /// Setting and changing [CupertinoTabBar.currentIndex] programmatically will + /// change the currently selected tab item in the [tabBar] as well as change + /// the currently focused tab from the [tabBuilder]. + + /// If [CupertinoTabBar.onTap] is provided, it will still be called. + /// [CupertinoTabScaffold] automatically also listen to the /// [CupertinoTabBar]'s `onTap` to change the [CupertinoTabBar]'s `currentIndex` /// and change the actively displayed tab in [CupertinoTabScaffold]'s own /// main content area. /// /// If translucent, the main content may slide behind it. /// Otherwise, the main content's bottom margin will be offset by its height. + /// + /// Must not be null. final CupertinoTabBar tabBar; /// An [IndexedWidgetBuilder] that's called when tabs become active. @@ -121,6 +128,8 @@ class CupertinoTabScaffold extends StatefulWidget { /// In that case, the child's [BuildContext]'s [MediaQuery] will have a /// bottom padding indicating the area of obstructing overlap from the /// [tabBar]. + /// + /// Must not be null. final IndexedWidgetBuilder tabBuilder; @override @@ -128,7 +137,21 @@ class CupertinoTabScaffold extends StatefulWidget { } class _CupertinoTabScaffoldState extends State { - int _currentPage = 0; + int _currentPage; + + @override + void initState() { + super.initState(); + _currentPage = widget.tabBar.currentIndex; + } + + @override + void didUpdateWidget(CupertinoTabScaffold oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.tabBar.currentIndex != oldWidget.tabBar.currentIndex) { + _currentPage = widget.tabBar.currentIndex; + } + } @override Widget build(BuildContext context) { diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index 7e62b4c80e..5ef397a3b7 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -237,9 +237,64 @@ void main() { 1, ); }); + + testWidgets('Programmatic tab switching', (WidgetTester tester) async { + final List tabsPainted = []; + + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + builder: (BuildContext context, Widget child) { + return new CupertinoTabScaffold( + tabBar: _buildTabBar(), + tabBuilder: (BuildContext context, int index) { + return new CustomPaint( + child: new Text('Page ${index + 1}'), + painter: new TestCallbackPainter( + onPaint: () { tabsPainted.add(index); } + ) + ); + }, + ); + }, + ), + ); + + expect(tabsPainted, [0]); + + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + builder: (BuildContext context, Widget child) { + return new CupertinoTabScaffold( + tabBar: _buildTabBar(selectedTab: 1), // Programmatically change the tab now. + tabBuilder: (BuildContext context, int index) { + return new CustomPaint( + child: new Text('Page ${index + 1}'), + painter: new TestCallbackPainter( + onPaint: () { tabsPainted.add(index); } + ) + ); + }, + ); + }, + ), + ); + + expect(tabsPainted, [0, 1]); + // onTap is not called when changing tabs programmatically. + expect(selectedTabs, isEmpty); + + // Can still tap out of the programmatically selected tab. + await tester.tap(find.text('Tab 1')); + await tester.pump(); + + expect(tabsPainted, [0, 1, 0]); + expect(selectedTabs, [0]); + }); } -CupertinoTabBar _buildTabBar() { +CupertinoTabBar _buildTabBar({ int selectedTab: 0 }) { return new CupertinoTabBar( items: const [ const BottomNavigationBarItem( @@ -252,6 +307,7 @@ CupertinoTabBar _buildTabBar() { ), ], backgroundColor: CupertinoColors.white, + currentIndex: selectedTab, onTap: (int newTab) => selectedTabs.add(newTab), ); } \ No newline at end of file