mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Create a CupertinoScaffold (#10543)
* works using pageview it’s a bit heavy and scrolls no documentation or test yet * docs and tests * more docs tweak * revert drive by page view changes * swap out the indexed stack for a custom thing * layout everything on the cheap tests pass * add more tests * move back to stack and add docs * review notes * build all the tabs once built
This commit is contained in:
parent
bb119e95fa
commit
e38f92df9a
@ -14,6 +14,7 @@ export 'src/cupertino/colors.dart';
|
||||
export 'src/cupertino/dialog.dart';
|
||||
export 'src/cupertino/nav_bar.dart';
|
||||
export 'src/cupertino/page.dart';
|
||||
export 'src/cupertino/scaffold.dart';
|
||||
export 'src/cupertino/slider.dart';
|
||||
export 'src/cupertino/switch.dart';
|
||||
export 'src/cupertino/thumb_painter.dart';
|
||||
|
||||
@ -31,7 +31,7 @@ const Color _kDefaultTabBarBorderColor = const Color(0x4C000000);
|
||||
/// default), it will produce a blurring effect to the content behind it.
|
||||
//
|
||||
// TODO(xster): document using with a CupertinoScaffold.
|
||||
class CupertinoTabBar extends StatelessWidget {
|
||||
class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// Creates a tab bar in the iOS style.
|
||||
CupertinoTabBar({
|
||||
Key key,
|
||||
@ -81,10 +81,14 @@ class CupertinoTabBar extends StatelessWidget {
|
||||
/// should configure itself to match the icon theme's size and color.
|
||||
final double iconSize;
|
||||
|
||||
/// True if the tab bar's background color has no transparency.
|
||||
bool get opaque => backgroundColor.alpha == 0xFF;
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(_kTabBarHeight);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool addBlur = backgroundColor.alpha != 0xFF;
|
||||
|
||||
Widget result = new DecoratedBox(
|
||||
decoration: new BoxDecoration(
|
||||
border: const Border(
|
||||
@ -120,7 +124,7 @@ class CupertinoTabBar extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
if (addBlur) {
|
||||
if (!opaque) {
|
||||
// For non-opaque backgrounds, apply a blur effect.
|
||||
result = new ClipRect(
|
||||
child: new BackdropFilter(
|
||||
@ -141,6 +145,7 @@ class CupertinoTabBar extends StatelessWidget {
|
||||
_wrapActiveItem(
|
||||
new Expanded(
|
||||
child: new GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap == null ? null : () { onTap(index); },
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
@ -175,4 +180,28 @@ class CupertinoTabBar extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a clone of the current [CupertinoTabBar] but with provided
|
||||
/// parameters overriden.
|
||||
CupertinoTabBar copyWith({
|
||||
Key key,
|
||||
List<BottomNavigationBarItem> items,
|
||||
Color backgroundColor,
|
||||
Color activeColor,
|
||||
Color inactiveColor,
|
||||
Size iconSize,
|
||||
int currentIndex,
|
||||
ValueChanged<int> onTap,
|
||||
}) {
|
||||
return new CupertinoTabBar(
|
||||
key: key ?? this.key,
|
||||
items: items ?? this.items,
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
activeColor: activeColor ?? this.activeColor,
|
||||
inactiveColor: inactiveColor ?? this.inactiveColor,
|
||||
iconSize: iconSize ?? this.iconSize,
|
||||
currentIndex: currentIndex ?? this.currentIndex,
|
||||
onTap: onTap ?? this.onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,13 +69,14 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid
|
||||
/// The [title] remains black if it's a text as per iOS standard design.
|
||||
final Color actionsForegroundColor;
|
||||
|
||||
/// True if the nav bar's background color has no transparency.
|
||||
bool get opaque => backgroundColor.alpha == 0xFF;
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(_kNavBarHeight);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool addBlur = backgroundColor.alpha != 0xFF;
|
||||
|
||||
Widget styledMiddle = middle;
|
||||
if (styledMiddle.runtimeType == Text || styledMiddle.runtimeType == DefaultTextStyle) {
|
||||
// Let the middle be black rather than `actionsForegroundColor` in case
|
||||
@ -132,7 +133,7 @@ class CupertinoNavigationBar extends StatelessWidget implements PreferredSizeWid
|
||||
),
|
||||
);
|
||||
|
||||
if (addBlur) {
|
||||
if (!opaque) {
|
||||
// For non-opaque backgrounds, apply a blur effect.
|
||||
result = new ClipRect(
|
||||
child: new BackdropFilter(
|
||||
|
||||
229
packages/flutter/lib/src/cupertino/scaffold.dart
Normal file
229
packages/flutter/lib/src/cupertino/scaffold.dart
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'bottom_tab_bar.dart';
|
||||
import 'nav_bar.dart';
|
||||
|
||||
/// Implements a basic iOS application's layout and behavior structure.
|
||||
///
|
||||
/// The scaffold lays out the navigation bar on top, the tab bar at the bottom
|
||||
/// and tabbed or untabbed content between or behind the bars.
|
||||
///
|
||||
/// For tabbed scaffolds, the tab's active item and the actively showing tab
|
||||
/// in the content area are automatically connected.
|
||||
// TODO(xster): describe navigator handlings.
|
||||
// TODO(xster): add an example.
|
||||
class CupertinoScaffold extends StatefulWidget {
|
||||
/// Construct a [CupertinoScaffold] without tabs.
|
||||
///
|
||||
/// The [tabBar] and [rootTabPageBuilder] fields are not used in a [CupertinoScaffold]
|
||||
/// without tabs.
|
||||
// TODO(xster): document that page transitions will happen behind the navigation
|
||||
// bar.
|
||||
const CupertinoScaffold({
|
||||
Key key,
|
||||
this.navigationBar,
|
||||
@required this.child,
|
||||
}) : assert(child != null),
|
||||
tabBar = null,
|
||||
rootTabPageBuilder = null,
|
||||
super(key: key);
|
||||
|
||||
/// Construct a [CupertinoScaffold] with tabs.
|
||||
///
|
||||
/// A [tabBar] and a [rootTabPageBuilder] are required. The [CupertinoScaffold]
|
||||
/// will automatically listen to the provided [CupertinoTabBar]'s tap callbacks
|
||||
/// to change the active tab.
|
||||
///
|
||||
/// Tabs' contents are built with the provided [rootTabPageBuilder] at the active
|
||||
/// tab index. [rootTabPageBuilder] must be able to build the same number of
|
||||
/// pages as the [tabBar.items.length]. Inactive tabs will be moved [Offstage]
|
||||
/// and its animations disabled.
|
||||
///
|
||||
/// The [child] field is not used in a [CupertinoScaffold] with tabs.
|
||||
const CupertinoScaffold.tabbed({
|
||||
Key key,
|
||||
this.navigationBar,
|
||||
@required this.tabBar,
|
||||
@required this.rootTabPageBuilder,
|
||||
}) : assert(tabBar != null),
|
||||
assert(rootTabPageBuilder != null),
|
||||
child = null,
|
||||
super(key: key);
|
||||
|
||||
/// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
|
||||
/// top of the screen.
|
||||
///
|
||||
/// If translucent, the main content may slide behind it.
|
||||
/// Otherwise, the main content's top margin will be offset by its height.
|
||||
// TODO(xster): document its page transition animation when ready
|
||||
final PreferredSizeWidget navigationBar;
|
||||
|
||||
/// The [tabBar] is a [CupertinoTabBar] drawn at the bottom of the screen
|
||||
/// that lets the user switch between different tabs in the main content area
|
||||
/// when present.
|
||||
///
|
||||
/// This parameter is required and must be non-null when the [new CupertinoScaffold.tabbed]
|
||||
/// constructor is used.
|
||||
///
|
||||
/// When provided, [CupertinoTabBar.currentIndex] will be ignored and will
|
||||
/// be managed by the [CupertinoScaffold] to show the currently selected page
|
||||
/// as the active item index. If [CupertinoTabBar.onTap] is provided, it will
|
||||
/// still be called. [CupertinoScaffold] automatically also listen to the
|
||||
/// [CupertinoTabBar]'s `onTap` to change the [CupertinoTabBar]'s `currentIndex`
|
||||
/// and change the actively displayed tab in [CupertinoScaffold]'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.
|
||||
final CupertinoTabBar tabBar;
|
||||
|
||||
/// An [IndexedWidgetBuilder] that's called when tabs become active.
|
||||
///
|
||||
/// Used when a tabbed scaffold is constructed via the [new CupertinoScaffold.tabbed]
|
||||
/// constructor and must be non-null.
|
||||
///
|
||||
/// When the tab becomes inactive, its content is still cached in the widget
|
||||
/// tree [Offstage] and its animations disabled.
|
||||
///
|
||||
/// Content can slide under the [navigationBar] or the [tabBar] when they're
|
||||
/// translucent.
|
||||
final IndexedWidgetBuilder rootTabPageBuilder;
|
||||
|
||||
/// Widget to show in the main content area when the scaffold is used without
|
||||
/// tabs.
|
||||
///
|
||||
/// Used when the default [new CupertinoScaffold] constructor is used and must
|
||||
/// be non-null.
|
||||
///
|
||||
/// Content can slide under the [navigationBar] or the [tabBar] when they're
|
||||
/// translucent.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_CupertinoScaffoldState createState() => new _CupertinoScaffoldState();
|
||||
}
|
||||
|
||||
class _CupertinoScaffoldState extends State<CupertinoScaffold> {
|
||||
int _currentPage = 0;
|
||||
|
||||
/// Pad the given middle widget with or without top and bottom offsets depending
|
||||
/// on whether the middle widget should slide behind translucent bars.
|
||||
Widget _padMiddle(Widget middle) {
|
||||
double topPadding = MediaQuery.of(context).padding.top;
|
||||
if (widget.navigationBar is CupertinoNavigationBar) {
|
||||
final CupertinoNavigationBar top = widget.navigationBar;
|
||||
if (top.opaque)
|
||||
topPadding += top.preferredSize.height;
|
||||
}
|
||||
|
||||
double bottomPadding = 0.0;
|
||||
if (widget.tabBar?.opaque ?? false)
|
||||
bottomPadding = widget.tabBar.preferredSize.height;
|
||||
|
||||
return new Padding(
|
||||
padding: new EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
||||
child: middle,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> stacked = <Widget>[];
|
||||
|
||||
// The main content being at the bottom is added to the stack first.
|
||||
if (widget.child != null) {
|
||||
stacked.add(_padMiddle(widget.child));
|
||||
} else if (widget.rootTabPageBuilder != null) {
|
||||
stacked.add(_padMiddle(new _TabView(
|
||||
currentTabIndex: _currentPage,
|
||||
tabNumber: widget.tabBar.items.length,
|
||||
rootTabPageBuilder: widget.rootTabPageBuilder,
|
||||
)));
|
||||
}
|
||||
|
||||
if (widget.navigationBar != null) {
|
||||
stacked.add(new Align(
|
||||
alignment: FractionalOffset.topCenter,
|
||||
child: widget.navigationBar,
|
||||
));
|
||||
}
|
||||
|
||||
if (widget.tabBar != null) {
|
||||
stacked.add(new Align(
|
||||
alignment: FractionalOffset.bottomCenter,
|
||||
// Override the tab bar's currentIndex to the current tab and hook in
|
||||
// our own listener to update the _currentPage on top of a possibly user
|
||||
// provided callback.
|
||||
child: widget.tabBar.copyWith(
|
||||
currentIndex: _currentPage,
|
||||
onTap: (int newIndex) {
|
||||
setState(() {
|
||||
_currentPage = newIndex;
|
||||
});
|
||||
// Chain the user's original callback.
|
||||
if (widget.tabBar.onTap != null)
|
||||
widget.tabBar.onTap(newIndex);
|
||||
}
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return new Stack(
|
||||
children: stacked,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An widget laying out multiple tabs with only one active tab being built
|
||||
/// at a time and on stage. Off stage tabs' animations are stopped.
|
||||
class _TabView extends StatefulWidget {
|
||||
_TabView({
|
||||
@required this.currentTabIndex,
|
||||
@required this.tabNumber,
|
||||
@required this.rootTabPageBuilder,
|
||||
}) : assert(currentTabIndex != null),
|
||||
assert(tabNumber != null && tabNumber > 0),
|
||||
assert(rootTabPageBuilder != null);
|
||||
|
||||
final int currentTabIndex;
|
||||
final int tabNumber;
|
||||
final IndexedWidgetBuilder rootTabPageBuilder;
|
||||
|
||||
@override
|
||||
_TabViewState createState() => new _TabViewState();
|
||||
}
|
||||
|
||||
class _TabViewState extends State<_TabView> {
|
||||
List<Widget> tabs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
tabs = new List<Widget>(widget.tabNumber);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new Stack(
|
||||
children: new List<Widget>.generate(widget.tabNumber, (int index) {
|
||||
final bool active = index == widget.currentTabIndex;
|
||||
|
||||
// TODO(xster): lazily replace empty tabs with Navigators instead.
|
||||
if (active || tabs[index] != null)
|
||||
tabs[index] = widget.rootTabPageBuilder(context, index);
|
||||
|
||||
return new Offstage(
|
||||
offstage: !active,
|
||||
child: new TickerMode(
|
||||
enabled: active,
|
||||
child: tabs[index] ?? new Container(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1914,12 +1914,13 @@ class ListBody extends MultiChildRenderObjectWidget {
|
||||
/// placed relative to the stack according to their top, right, bottom, and left
|
||||
/// properties.
|
||||
///
|
||||
/// The stack paints its children in order. If you want to change the order in
|
||||
/// which the children paint, you can rebuild the stack with the children in
|
||||
/// the new order. If you reorder the children in this way, consider giving the
|
||||
/// children non-null keys. These keys will cause the framework to move the
|
||||
/// underlying objects for the children to their new locations rather than
|
||||
/// recreate them at their new location.
|
||||
/// The stack paints its children in order with the first child being at the
|
||||
/// bottom. If you want to change the order in which the children paint, you
|
||||
/// can rebuild the stack with the children in the new order. If you reorder
|
||||
/// the children in this way, consider giving the children non-null keys.
|
||||
/// These keys will cause the framework to move the underlying objects for
|
||||
/// the children to their new locations rather than recreate them at their
|
||||
/// new location.
|
||||
///
|
||||
/// For more details about the stack layout algorithm, see [RenderStack].
|
||||
///
|
||||
|
||||
@ -83,7 +83,7 @@ class _ToolbarLayout extends MultiChildLayoutDelegate {
|
||||
// If false the middle widget should be left justified within the space
|
||||
// between the leading and trailing widgets.
|
||||
// If true the middle widget is centered within the toolbar (not within the horizontal
|
||||
// space bewteen the leading and trailing widgets).
|
||||
// space between the leading and trailing widgets).
|
||||
// TODO(xster): document RTL once supported.
|
||||
final bool centerMiddle;
|
||||
|
||||
|
||||
203
packages/flutter/test/cupertino/scaffold_test.dart
Normal file
203
packages/flutter/test/cupertino/scaffold_test.dart
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/rendering_tester.dart';
|
||||
import '../services/mocks_for_image_cache.dart';
|
||||
|
||||
List<int> selectedTabs;
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
selectedTabs = <int>[];
|
||||
});
|
||||
|
||||
testWidgets('Contents are behind translucent bar', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
// TODO(xster): change to a CupertinoPageRoute.
|
||||
return new PageRouteBuilder<Null>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return const CupertinoScaffold(
|
||||
// Default nav bar is translucent.
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
middle: const Text('Title'),
|
||||
),
|
||||
child: const Center(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getTopLeft(find.byType(Center)), const Offset(0.0, 0.0));
|
||||
});
|
||||
|
||||
testWidgets('Contents are between opaque bars', (WidgetTester tester) async {
|
||||
final Center page1Center = const Center();
|
||||
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
// TODO(xster): change to a CupertinoPageRoute.
|
||||
return new PageRouteBuilder<Null>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return new CupertinoScaffold.tabbed(
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
backgroundColor: CupertinoColors.white,
|
||||
middle: const Text('Title'),
|
||||
),
|
||||
tabBar: _buildTabBar(),
|
||||
rootTabPageBuilder: (BuildContext context, int index) {
|
||||
return index == 0 ? page1Center : new Stack();
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byWidget(page1Center)).height, 600.0 - 44.0 - 50.0);
|
||||
});
|
||||
|
||||
testWidgets('Tab switching', (WidgetTester tester) async {
|
||||
final List<int> tabsPainted = <int>[];
|
||||
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
// TODO(xster): change to a CupertinoPageRoute.
|
||||
return new PageRouteBuilder<Null>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return new CupertinoScaffold.tabbed(
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
backgroundColor: CupertinoColors.white,
|
||||
middle: const Text('Title'),
|
||||
),
|
||||
tabBar: _buildTabBar(),
|
||||
rootTabPageBuilder: (BuildContext context, int index) {
|
||||
return new CustomPaint(
|
||||
child: new Text('Page ${index + 1}'),
|
||||
painter: new TestCallbackPainter(
|
||||
onPaint: () { tabsPainted.add(index); }
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(tabsPainted, <int>[0]);
|
||||
RichText tab1 = tester.widget(find.descendant(
|
||||
of: find.text('Tab 1'),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(tab1.text.style.color, CupertinoColors.activeBlue);
|
||||
RichText tab2 = tester.widget(find.descendant(
|
||||
of: find.text('Tab 2'),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(tab2.text.style.color, CupertinoColors.inactiveGray);
|
||||
|
||||
await tester.tap(find.text('Tab 2'));
|
||||
await tester.pump();
|
||||
|
||||
expect(tabsPainted, <int>[0, 1]);
|
||||
tab1 = tester.widget(find.descendant(
|
||||
of: find.text('Tab 1'),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(tab1.text.style.color, CupertinoColors.inactiveGray);
|
||||
tab2 = tester.widget(find.descendant(
|
||||
of: find.text('Tab 2'),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(tab2.text.style.color, CupertinoColors.activeBlue);
|
||||
|
||||
await tester.tap(find.text('Tab 1'));
|
||||
await tester.pump();
|
||||
|
||||
expect(tabsPainted, <int>[0, 1, 0]);
|
||||
});
|
||||
|
||||
testWidgets('Tabs are lazy built and moved offstage when inactive', (WidgetTester tester) async {
|
||||
final List<int> tabsBuilt = <int>[];
|
||||
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
// TODO(xster): change to a CupertinoPageRoute.
|
||||
return new PageRouteBuilder<Null>(
|
||||
settings: settings,
|
||||
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return new CupertinoScaffold.tabbed(
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
backgroundColor: CupertinoColors.white,
|
||||
middle: const Text('Title'),
|
||||
),
|
||||
tabBar: _buildTabBar(),
|
||||
rootTabPageBuilder: (BuildContext context, int index) {
|
||||
tabsBuilt.add(index);
|
||||
return new Text('Page ${index + 1}');
|
||||
}
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
expect(tabsBuilt, <int>[0]);
|
||||
expect(find.text('Page 1'), findsOneWidget);
|
||||
expect(find.text('Page 2'), findsNothing);
|
||||
|
||||
await tester.tap(find.text('Tab 2'));
|
||||
await tester.pump();
|
||||
|
||||
// Both tabs are built but only one is onstage.
|
||||
expect(tabsBuilt, <int>[0, 0, 1]);
|
||||
expect(find.text('Page 1', skipOffstage: false), isOffstage);
|
||||
expect(find.text('Page 2'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Tab 1'));
|
||||
await tester.pump();
|
||||
|
||||
expect(tabsBuilt, <int>[0, 0, 1, 0, 1]);
|
||||
expect(find.text('Page 1'), findsOneWidget);
|
||||
expect(find.text('Page 2', skipOffstage: false), isOffstage);
|
||||
});
|
||||
}
|
||||
|
||||
CupertinoTabBar _buildTabBar() {
|
||||
return new CupertinoTabBar(
|
||||
items: <BottomNavigationBarItem>[
|
||||
const BottomNavigationBarItem(
|
||||
icon: const ImageIcon(const TestImageProvider(24, 24)),
|
||||
title: const Text('Tab 1'),
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: const ImageIcon(const TestImageProvider(24, 24)),
|
||||
title: const Text('Tab 2'),
|
||||
),
|
||||
],
|
||||
backgroundColor: CupertinoColors.white,
|
||||
onTap: (int newTab) => selectedTabs.add(newTab),
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user