Commit 9de4df1e authored by Hans Muller's avatar Hans Muller

TabNavigator animates selected TabView

The TabBar's selection is now represented by a TabBarSelection object which encapsulates both the previous and currently selected indices and the Performance used to animate the selection indicator.

Added a TabBarView class which displays a tab's contents. It uses a shared TabBarSelection to stay in sync with a TabBar. The TabBarView scrolls in sync with the TabBar when the selection changes. Eventually it will allow one to fling the selection forward or backwards.

Added a tabBar property to ToolBar. Typically the corresponding TabBarView will be the body of the toolbar's Scaffold.

Removed TabNavigatorView and TabNavigator.

Added a widget gallery tabs demo page. Removed the old tabs demo.
parent 40b3cf3f
......@@ -3,3 +3,9 @@ material-design-icons:
- name: navigation/arrow_drop_down
- name: navigation/cancel
- name: navigation/menu
- name: action/event
- name: action/home
- name: action/android
- name: action/alarm
- name: action/face
- name: action/language
// Copyright 2015 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/material.dart';
import 'widget_demo.dart';
final TabBarSelection _selection = new TabBarSelection();
final List<String> _iconNames = <String>["event", "home", "android", "alarm", "face", "language"];
Widget buildTabBar(_) {
return new TabBar(
selection: _selection,
isScrollable: true,
labels: _iconNames.map((String iconName) => new TabLabel(text: iconName, icon: "action/$iconName")).toList()
);
}
class TabsDemo extends StatefulComponent {
_TabsDemoState createState() => new _TabsDemoState();
}
class _TabsDemoState extends State<TabsDemo> {
double _viewWidth = 100.0;
void _handleSizeChanged(Size newSize) {
setState(() {
_viewWidth = newSize.width;
});
}
Widget build(_) {
return new SizeObserver(
onSizeChanged: _handleSizeChanged,
child: new TabBarView<String>(
selection: _selection,
items: _iconNames,
itemExtent: _viewWidth,
itemBuilder: (BuildContext context, String iconName, int index) {
return new Container(
key: new ValueKey<String>(iconName),
padding: const EdgeDims.all(12.0),
child: new Card(
child: new Center(child: new Icon(icon: "action/$iconName", size:IconSize.s48))
)
);
}
)
);
}
}
final WidgetDemo kTabsDemo = new WidgetDemo(
title: 'Tabs',
routeName: '/tabs',
tabBarBuilder: buildTabBar,
builder: (_) => new TabsDemo()
);
......@@ -5,9 +5,10 @@
import 'package:flutter/material.dart';
class WidgetDemo {
WidgetDemo({ this.title, this.routeName, this.builder });
WidgetDemo({ this.title, this.routeName, this.tabBarBuilder, this.builder });
final String title;
final String routeName;
final WidgetBuilder tabBarBuilder;
final WidgetBuilder builder;
}
......@@ -40,6 +40,11 @@ class GalleryPage extends StatelessComponent {
);
}
Widget _tabBar(BuildContext context) {
final WidgetBuilder builder = active?.tabBarBuilder;
return builder != null ? builder(context) : null;
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: new ToolBar(
......@@ -47,7 +52,8 @@ class GalleryPage extends StatelessComponent {
icon: 'navigation/menu',
onPressed: () { _showDrawer(context); }
),
center: new Text(active?.title ?? 'Material gallery')
center: new Text(active?.title ?? 'Material gallery'),
tabBar: _tabBar(context)
),
body: _body(context)
);
......
......@@ -9,6 +9,7 @@ import 'demo/date_picker_demo.dart';
import 'demo/drop_down_demo.dart';
import 'demo/selection_controls_demo.dart';
import 'demo/slider_demo.dart';
import 'demo/tabs_demo.dart';
import 'demo/time_picker_demo.dart';
import 'demo/widget_demo.dart';
import 'gallery_page.dart';
......@@ -18,6 +19,7 @@ final List<WidgetDemo> _kDemos = <WidgetDemo>[
kSelectionControlsDemo,
kSliderDemo,
kDatePickerDemo,
kTabsDemo,
kTimePickerDemo,
kDropDownDemo,
];
......
......@@ -6,6 +6,8 @@ part of stocks;
typedef void ModeUpdater(StockMode mode);
enum StockHomeTab { market, portfolio }
class StockHome extends StatefulComponent {
StockHome(this.stocks, this.symbols, this.stockMode, this.modeUpdater);
......@@ -20,6 +22,7 @@ class StockHome extends StatefulComponent {
class StockHomeState extends State<StockHome> {
final GlobalKey scaffoldKey = new GlobalKey();
final TabBarSelection _tabBarSelection = new TabBarSelection();
bool _isSearching = false;
String _searchQuery;
......@@ -160,7 +163,13 @@ class StockHomeState extends State<StockHome> {
icon: "navigation/more_vert",
onPressed: _handleMenuShow
)
]
],
tabBar: new TabBar(
selection: _tabBarSelection,
labels: <TabLabel>[
const TabLabel(text: 'MARKET'),
const TabLabel(text: 'PORTFOLIO')]
)
);
}
......@@ -208,25 +217,6 @@ class StockHomeState extends State<StockHome> {
static const List<String> portfolioSymbols = const <String>["AAPL","FIZZ", "FIVE", "FLAT", "ZINC", "ZNGA"];
Widget buildTabNavigator() {
return new TabNavigator(
views: <TabNavigatorView>[
new TabNavigatorView(
label: const TabLabel(text: 'MARKET'),
builder: (BuildContext context) => buildStockList(context, _filterBySearchQuery(_getStockList(config.symbols)).toList())
),
new TabNavigatorView(
label: const TabLabel(text: 'PORTFOLIO'),
builder: (BuildContext context) => buildStockList(context, _filterBySearchQuery(_getStockList(portfolioSymbols)).toList())
)
],
selectedIndex: selectedTabIndex,
onChanged: (int tabIndex) {
setState(() { selectedTabIndex = tabIndex; } );
}
);
}
static GlobalKey searchFieldKey = new GlobalKey();
static GlobalKey companyNameKey = new GlobalKey();
......@@ -270,12 +260,43 @@ class StockHomeState extends State<StockHome> {
);
}
Widget buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
return new Container(
key: new ValueKey<StockHomeTab>(tab),
child: buildStockList(context, _filterBySearchQuery(_getStockList(stockSymbols)).toList())
);
}
double _viewWidth = 100.0;
void _handleSizeChanged(Size newSize) {
setState(() {
_viewWidth = newSize.width;
});
}
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
body: buildTabNavigator(),
floatingActionButton: buildFloatingActionButton()
floatingActionButton: buildFloatingActionButton(),
body: new SizeObserver(
onSizeChanged: _handleSizeChanged,
child: new TabBarView<StockHomeTab>(
selection: _tabBarSelection,
items: [StockHomeTab.market, StockHomeTab.portfolio],
itemExtent: _viewWidth,
itemBuilder: (BuildContext context, StockHomeTab tab, _) {
switch (tab) {
case StockHomeTab.market:
return buildStockTab(context, tab, config.symbols);
case StockHomeTab.portfolio:
return buildStockTab(context, tab, portfolioSymbols);
default:
assert(false);
}
}
)
)
);
}
}
// Copyright 2015 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/material.dart';
import 'package:flutter/painting.dart';
class TabbedNavigatorApp extends StatefulComponent {
TabbedNavigatorAppState createState() => new TabbedNavigatorAppState();
}
class TabbedNavigatorAppState extends State<TabbedNavigatorApp> {
// The index of the selected tab for each of the TabNavigators constructed below.
List<int> selectedIndices = new List<int>.filled(5, 0);
TabNavigator _buildTabNavigator(int n, List<TabNavigatorView> views, Key key, {isScrollable: false}) {
return new TabNavigator(
key: key,
views: views,
selectedIndex: selectedIndices[n],
isScrollable: isScrollable,
onChanged: (int tabIndex) {
setState(() { selectedIndices[n] = tabIndex; } );
}
);
}
Widget _buildContent(String label) {
return new Center(
child: new Text(label, style: const TextStyle(fontSize: 48.0, fontWeight: FontWeight.w800))
);
}
TabNavigator _buildTextLabelsTabNavigator(int n) {
Iterable<TabNavigatorView> views = ["ONE", "TWO", "FREE", "FOUR"]
.map((text) {
return new TabNavigatorView(
label: new TabLabel(text: text),
builder: (BuildContext context) => _buildContent(text)
);
});
return _buildTabNavigator(n, views.toList(), const ValueKey<String>('textLabelsTabNavigator'));
}
TabNavigator _buildIconLabelsTabNavigator(int n) {
Iterable<TabNavigatorView> views = ["event", "home", "android", "alarm", "face", "language"]
.map((String iconName) {
return new TabNavigatorView(
label: new TabLabel(icon: "action/$iconName"),
builder: (BuildContext context) => _buildContent(iconName)
);
});
return _buildTabNavigator(n, views.toList(), const ValueKey<String>('iconLabelsTabNavigator'));
}
TabNavigator _buildTextAndIconLabelsTabNavigator(int n) {
List<TabNavigatorView> views = <TabNavigatorView>[
new TabNavigatorView(
label: const TabLabel(text: 'STOCKS', icon: 'action/list'),
builder: (BuildContext context) => _buildContent("Stocks")
),
new TabNavigatorView(
label: const TabLabel(text: 'PORTFOLIO', icon: 'action/account_circle'),
builder: (BuildContext context) => _buildContent("Portfolio")
),
new TabNavigatorView(
label: const TabLabel(text: 'SUMMARY', icon: 'action/assessment'),
builder: (BuildContext context) => _buildContent("Summary")
)
];
return _buildTabNavigator(n, views, const ValueKey<String>('textAndIconLabelsTabNavigator'));
}
TabNavigator _buildScrollableTabNavigator(int n) {
Iterable<TabNavigatorView> views = [
"MIN WIDTH",
"THIS TAB LABEL IS SO WIDE THAT IT OCCUPIES TWO LINES",
"THIS TAB IS PRETTY WIDE TOO",
"MORE",
"TABS",
"TO",
"STRETCH",
"OUT",
"THE",
"TAB BAR"
]
.map((text) {
return new TabNavigatorView(
label: new TabLabel(text: text),
builder: (BuildContext context) => _buildContent(text)
);
});
return _buildTabNavigator(n, views.toList(), const ValueKey<String>('scrollableTabNavigator'), isScrollable: true);
}
Container _buildCard(BuildContext context, TabNavigator tabNavigator) {
return new Container(
padding: const EdgeDims.all(12.0),
child: new Card(child: new Padding(child: tabNavigator, padding: const EdgeDims.all(8.0)))
);
}
Widget build(BuildContext context) {
List<TabNavigatorView> views = <TabNavigatorView>[
new TabNavigatorView(
label: const TabLabel(text: 'TEXT'),
builder: (BuildContext context) => _buildCard(context, _buildTextLabelsTabNavigator(0))
),
new TabNavigatorView(
label: const TabLabel(text: 'ICONS'),
builder: (BuildContext context) => _buildCard(context, _buildIconLabelsTabNavigator(1))
),
new TabNavigatorView(
label: const TabLabel(text: 'BOTH'),
builder: (BuildContext context) => _buildCard(context, _buildTextAndIconLabelsTabNavigator(2))
),
new TabNavigatorView(
label: const TabLabel(text: 'SCROLL'),
builder: (BuildContext context) => _buildCard(context, _buildScrollableTabNavigator(3))
)
];
TabNavigator tabNavigator = _buildTabNavigator(4, views, const ValueKey<String>('tabs'));
assert(selectedIndices.length == 5);
ToolBar toolbar = new ToolBar(
center: new Text('Tabbed Navigator', style: Typography.white.title),
elevation: 0
);
return new Scaffold(
toolBar: toolbar,
body: tabNavigator
);
}
}
void main() {
runApp(new MaterialApp(
title: 'Tabs',
routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new TabbedNavigatorApp(),
}
));
}
......@@ -292,14 +292,12 @@ class TabLabel {
}
}
class Tab extends StatelessComponent {
Tab({
class _Tab extends StatelessComponent {
_Tab({
Key key,
this.onSelected,
this.label,
this.color,
this.selected: false,
this.selectedColor
this.color
}) : super(key: key) {
assert(label.text != null || label.icon != null);
}
......@@ -307,19 +305,16 @@ class Tab extends StatelessComponent {
final VoidCallback onSelected;
final TabLabel label;
final Color color;
final bool selected;
final Color selectedColor;
Widget _buildLabelText() {
assert(label.text != null);
TextStyle style = new TextStyle(color: selected ? selectedColor : color);
TextStyle style = new TextStyle(color: color);
return new Text(label.text, style: style);
}
Widget _buildLabelIcon() {
assert(label.icon != null);
Color iconColor = selected ? selectedColor : color;
ColorFilter filter = new ColorFilter.mode(iconColor, TransferMode.srcATop);
ColorFilter filter = new ColorFilter.mode(color, TransferMode.srcATop);
return new Icon(icon: label.icon, colorFilter: filter);
}
......@@ -381,18 +376,46 @@ class _TabsScrollBehavior extends BoundedBehavior {
}
}
class TabBarSelection {
TabBarSelection({ int index: 0, this.onChanged }) : _index = index;
final VoidCallback onChanged;
PerformanceView get performance => _performance.view;
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
int get index => _index;
int _index;
void set index(int value) {
if (value == _index)
return;
_previousIndex = _index;
_index = value;
_performance
..progress = 0.0
..play().then((_) {
if (onChanged != null)
onChanged();
});
}
int get previousIndex => _previousIndex;
int _previousIndex = 0;
}
class TabBar extends Scrollable {
TabBar({
Key key,
this.labels,
this.selectedIndex: 0,
this.onChanged,
this.selection,
this.isScrollable: false
}) : super(key: key, scrollDirection: ScrollDirection.horizontal);
}) : super(key: key, scrollDirection: ScrollDirection.horizontal) {
assert(labels != null);
assert(selection != null);
}
final Iterable<TabLabel> labels;
final int selectedIndex;
final TabSelectedIndexChanged onChanged;
final TabBarSelection selection;
final bool isScrollable;
_TabBarState createState() => new _TabBarState();
......@@ -401,36 +424,53 @@ class TabBar extends Scrollable {
class _TabBarState extends ScrollableState<TabBar> {
void initState() {
super.initState();
_indicatorAnimation = new ValuePerformance<Rect>()
..duration = _kTabBarScroll
..variable = new AnimatedRectValue(null, curve: Curves.ease);
scrollBehavior.isScrollable = config.isScrollable;
config.selection._performance
..addStatusListener(_handleStatusChange)
..addListener(_handleProgressChange);
}
Size _tabBarSize;
Size _viewportSize = Size.zero;
List<double> _tabWidths;
ValuePerformance<Rect> _indicatorAnimation;
void didUpdateConfig(TabBar oldConfig) {
super.didUpdateConfig(oldConfig);
if (!config.isScrollable)
scrollTo(0.0);
void dispose() {
config.selection._performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange)
..stop();
super.dispose();
}
AnimatedRectValue get _indicatorRect => _indicatorAnimation.variable;
Performance get _performance => config.selection._performance;
bool _indicatorRectIsValid = false;
void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) {
// The performance's status change is our indication that the selection index has
// changed. We don't start animating the _indicatorRect until after we've reset
// _indicatorRect here.
void _handleStatusChange(PerformanceStatus status) {
_indicatorRectIsValid = status == PerformanceStatus.forward;
if (status == PerformanceStatus.forward) {
if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll);
setState(() {
_indicatorRect
..begin = (_indicatorRect.value == null ? _tabIndicatorRect(fromTabIndex) : _indicatorRect.value)
..end = _tabIndicatorRect(toTabIndex);
_indicatorAnimation
..progress = 0.0
..play();
..begin = _indicatorRect.value ?? _tabIndicatorRect(config.selection.previousIndex)
..end = _tabIndicatorRect(config.selection.index);
});
}
}
ScrollBehavior createScrollBehavior() => new _TabsScrollBehavior();
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleProgressChange() {
// Performance listeners are notified before statusListeners.
if (_indicatorRectIsValid && _performance.status == PerformanceStatus.forward) {
setState(() {
_indicatorRect.setProgress(_performance.progress, AnimationDirection.forward);
});
}
}
Size _viewportSize = Size.zero;
Size _tabBarSize;
List<double> _tabWidths;
AnimatedRectValue _indicatorRect = new AnimatedRectValue(null, curve: Curves.ease);
Rect _tabRect(int tabIndex) {
assert(_tabBarSize != null);
......@@ -450,31 +490,40 @@ class _TabBarState extends ScrollableState<TabBar> {
return new Rect.fromLTRB(r.left, r.bottom, r.right, r.bottom + _kTabIndicatorHeight);
}
void didUpdateConfig(TabBar oldConfig) {
super.didUpdateConfig(oldConfig);
if (!config.isScrollable)
scrollTo(0.0);
}
ScrollBehavior createScrollBehavior() => new _TabsScrollBehavior();
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
double _centeredTabScrollOffset(int tabIndex) {
double viewportWidth = scrollBehavior.containerExtent;
return (_tabRect(tabIndex).left + _tabWidths[tabIndex] / 2.0 - viewportWidth / 2.0)
Rect tabRect = _tabRect(tabIndex);
return (tabRect.left + tabRect.width / 2.0 - viewportWidth / 2.0)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
void _handleTabSelected(int tabIndex) {
if (tabIndex != config.selectedIndex) {
if (_tabWidths != null) {
if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(tabIndex), duration: _kTabBarScroll);
_startIndicatorAnimation(config.selectedIndex, tabIndex);
}
if (config.onChanged != null)
config.onChanged(tabIndex);
}
if (tabIndex != config.selection.index)
setState(() {
config.selection.index = tabIndex;
});
}
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
return new Tab(
onSelected: () => _handleTabSelected(tabIndex),
Color labelColor = color;
if (tabIndex == config.selection.index)
labelColor = Color.lerp(color, selectedColor, _performance.progress);
else if (tabIndex == config.selection.previousIndex)
labelColor = Color.lerp(selectedColor, color, _performance.progress);
return new _Tab(
onSelected: () { _handleTabSelected(tabIndex); },
label: label,
color: color,
selected: tabIndex == config.selectedIndex,
selectedColor: selectedColor
color: labelColor
);
}
......@@ -496,6 +545,8 @@ class _TabBarState extends ScrollableState<TabBar> {
void _handleViewportSizeChanged(Size newSize) {
_viewportSize = newSize;
_updateScrollBehavior();
if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll);
}
Widget buildContent(BuildContext context) {
......@@ -526,11 +577,11 @@ class _TabBarState extends ScrollableState<TabBar> {
style: textStyle,
child: new BuilderTransition(
variables: <AnimatedValue<Rect>>[_indicatorRect],
performance: _indicatorAnimation.view,
performance: config.selection.performance,
builder: (BuildContext context) {
return new _TabBarWrapper(
children: tabs,
selectedIndex: config.selectedIndex,
selectedIndex: config.selection.index,
indicatorColor: indicatorColor,
indicatorRect: _indicatorRect.value,
textAndIcons: textAndIcons,
......@@ -563,46 +614,111 @@ class _TabBarState extends ScrollableState<TabBar> {
}
}
class TabNavigatorView {
TabNavigatorView({ this.label, this.builder }) {
assert(builder != null);
class TabBarView<T> extends ScrollableList<T> {
TabBarView({
Key key,
this.selection,
List<T> items,
ItemBuilder<T> itemBuilder,
double itemExtent
}) : super(
key: key,
scrollDirection: ScrollDirection.horizontal,
items: items,
itemBuilder: itemBuilder,
itemExtent: itemExtent,
itemsWrap: false
) {
assert(selection != null);
}
// this uses a builder for the contents, rather than a raw Widget child,
// because there might be many, many tabs and some might be relatively
// expensive to create up front. This way, the view is only created lazily.
final TabBarSelection selection;
final TabLabel label;
final WidgetBuilder builder;
_TabBarViewState createState() => new _TabBarViewState<T>();
}
class TabNavigator extends StatelessComponent {
TabNavigator({
Key key,
this.views,
this.selectedIndex: 0,
this.onChanged,
this.isScrollable: false
}) : super(key: key);
// TODO(hansmuller): horizontal scrolling should drive the TabSelection's performance.
class _NotScrollable extends BoundedBehavior {
bool get isScrollable => false;
}
final List<TabNavigatorView> views;
final int selectedIndex;
final TabSelectedIndexChanged onChanged;
final bool isScrollable;
class _TabBarViewState<T> extends ScrollableListState<T, TabBarView<T>> {
Widget build(BuildContext context) {
assert(views != null && views.isNotEmpty);
assert(selectedIndex >= 0 && selectedIndex < views.length);
return new Column(<Widget>[
new TabBar(
labels: views.map((TabNavigatorView view) => view.label),
onChanged: onChanged,
selectedIndex: selectedIndex,
isScrollable: isScrollable
),
new Flexible(child: views[selectedIndex].builder(context))
],
alignItems: FlexAlignItems.stretch
);
ScrollBehavior createScrollBehavior() => new _NotScrollable();
List<int> _itemIndices = [0, 1];
AnimationDirection _scrollDirection = AnimationDirection.forward;
void _initItemIndicesAndScrollPosition() {
final int selectedIndex = config.selection.index;
if (selectedIndex == 0) {
_itemIndices = <int>[0, 1];
scrollTo(0.0);
} else if (selectedIndex == config.items.length - 1) {
_itemIndices = <int>[selectedIndex - 1, selectedIndex];
scrollTo(config.itemExtent);
} else {
_itemIndices = <int>[selectedIndex - 1, selectedIndex, selectedIndex + 1];
scrollTo(config.itemExtent);
}
}
Performance get _performance => config.selection._performance;
void initState() {
super.initState();
_initItemIndicesAndScrollPosition();
_performance
..addStatusListener(_handleStatusChange)
..addListener(_handleProgressChange);
}
void dispose() {
_performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange)
..stop();
super.dispose();
}
void didUpdateConfig(TabBarView oldConfig) {
super.didUpdateConfig(oldConfig);
if (oldConfig.itemExtent != config.itemExtent && !_performance.isAnimating)
_initItemIndicesAndScrollPosition();
}
void _handleStatusChange(PerformanceStatus status) {
final int selectedIndex = config.selection.index;
final int previousSelectedIndex = config.selection.previousIndex;
if (status == PerformanceStatus.forward) {
if (selectedIndex < previousSelectedIndex) {
_itemIndices = <int>[selectedIndex, previousSelectedIndex];
_scrollDirection = AnimationDirection.reverse;
} else {
_itemIndices = <int>[previousSelectedIndex, selectedIndex];
_scrollDirection = AnimationDirection.forward;
}
} else if (status == PerformanceStatus.completed) {
_initItemIndicesAndScrollPosition();
}
}
void _handleProgressChange() {
if (_scrollDirection == AnimationDirection.forward)
scrollTo(config.itemExtent * _performance.progress);
else
scrollTo(config.itemExtent * (1.0 - _performance.progress));
}
int get itemCount => _itemIndices.length;
List<Widget> buildItems(BuildContext context, int start, int count) {
return _itemIndices
.skip(start)
.take(count)
.map((int i) => config.itemBuilder(context, config.items[i], i))
.toList();
}
}
......@@ -8,6 +8,7 @@ import 'constants.dart';
import 'icon_theme.dart';
import 'icon_theme_data.dart';
import 'shadows.dart';
import 'tabs.dart';
import 'theme.dart';
import 'typography.dart';
......@@ -18,6 +19,7 @@ class ToolBar extends StatelessComponent {
this.center,
this.right,
this.bottom,
this.tabBar,
this.elevation: 4,
this.backgroundColor,
this.textTheme,
......@@ -28,6 +30,7 @@ class ToolBar extends StatelessComponent {
final Widget center;
final List<Widget> right;
final Widget bottom;
final TabBar tabBar;
final int elevation;
final Color backgroundColor;
final TextTheme textTheme;
......@@ -40,6 +43,7 @@ class ToolBar extends StatelessComponent {
center: center,
right: right,
bottom: bottom,
tabBar: tabBar,
elevation: elevation,
backgroundColor: backgroundColor,
textTheme: textTheme,
......@@ -90,6 +94,9 @@ class ToolBar extends StatelessComponent {
child: new Container(height: kExtendedToolBarHeight - kToolBarHeight, child: bottom)
));
if (tabBar != null)
columnChildren.add(tabBar);
Widget content = new AnimatedContainer(
duration: kThemeChangeDuration,
padding: new EdgeDims.symmetric(horizontal: 8.0),
......
......@@ -7,16 +7,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
int selectedIndex = 2;
TabBarSelection selection;
Widget buildFrame({ List<String> tabs, bool isScrollable: false }) {
return new TabBar(
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(),
selectedIndex: selectedIndex,
isScrollable: isScrollable,
onChanged: (int tabIndex) {
selectedIndex = tabIndex;
}
selection: selection,
isScrollable: isScrollable
);
}
......@@ -24,56 +21,56 @@ void main() {
test('TabBar tap selects tab', () {
testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C'];
selectedIndex = 2;
selection = new TabBarSelection(index: 2);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
expect(tester.findText('A'), isNotNull);
expect(tester.findText('B'), isNotNull);
expect(tester.findText('C'), isNotNull);
expect(selectedIndex, equals(2));
expect(selection.index, equals(2));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
tester.tap(tester.findText('B'));
tester.pump();
expect(selectedIndex, equals(1));
expect(selection.index, equals(1));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
tester.tap(tester.findText('C'));
tester.pump();
expect(selectedIndex, equals(2));
expect(selection.index, equals(2));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
tester.tap(tester.findText('A'));
tester.pump();
expect(selectedIndex, equals(0));
expect(selection.index, equals(0));
});
});
test('Scrollable TabBar tap selects tab', () {
testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C'];
selectedIndex = 2;
selection = new TabBarSelection(index: 2);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
expect(tester.findText('A'), isNotNull);
expect(tester.findText('B'), isNotNull);
expect(tester.findText('C'), isNotNull);
expect(selectedIndex, equals(2));
expect(selection.index, equals(2));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
tester.tap(tester.findText('B'));
tester.pump();
expect(selectedIndex, equals(1));
expect(selection.index, equals(1));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
tester.tap(tester.findText('C'));
tester.pump();
expect(selectedIndex, equals(2));
expect(selection.index, equals(2));
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
tester.tap(tester.findText('A'));
tester.pump();
expect(selectedIndex, equals(0));
expect(selection.index, equals(0));
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment