Commit 6178fdff authored by Hans Muller's avatar Hans Muller

TabBarSelection is now expected to be an ancestor of its TabBar and TabBarView.

parent 3f32201c
...@@ -7,11 +7,16 @@ import 'package:flutter/material.dart'; ...@@ -7,11 +7,16 @@ import 'package:flutter/material.dart';
import 'widget_demo.dart'; import 'widget_demo.dart';
final List<String> _iconNames = <String>["event", "home", "android", "alarm", "face", "language"]; final List<String> _iconNames = <String>["event", "home", "android", "alarm", "face", "language"];
final TabBarSelection _selection = new TabBarSelection(maxIndex: _iconNames.length - 1);
Widget buildTabBar(_) { Widget _buildTabBarSelection(_, Widget child) {
return new TabBarSelection(
maxIndex: _iconNames.length - 1,
child: child
);
}
Widget _buildTabBar(_) {
return new TabBar( return new TabBar(
selection: _selection,
isScrollable: true, isScrollable: true,
labels: _iconNames.map((String iconName) => new TabLabel(text: iconName, icon: "action/$iconName")).toList() labels: _iconNames.map((String iconName) => new TabLabel(text: iconName, icon: "action/$iconName")).toList()
); );
...@@ -24,7 +29,6 @@ class TabsDemo extends StatefulComponent { ...@@ -24,7 +29,6 @@ class TabsDemo extends StatefulComponent {
class _TabsDemoState extends State<TabsDemo> { class _TabsDemoState extends State<TabsDemo> {
Widget build(_) { Widget build(_) {
return new TabBarView<String>( return new TabBarView<String>(
selection: _selection,
items: _iconNames, items: _iconNames,
itemBuilder: (BuildContext context, String iconName, int index) { itemBuilder: (BuildContext context, String iconName, int index) {
return new Container( return new Container(
...@@ -42,6 +46,7 @@ class _TabsDemoState extends State<TabsDemo> { ...@@ -42,6 +46,7 @@ class _TabsDemoState extends State<TabsDemo> {
final WidgetDemo kTabsDemo = new WidgetDemo( final WidgetDemo kTabsDemo = new WidgetDemo(
title: 'Tabs', title: 'Tabs',
routeName: '/tabs', routeName: '/tabs',
tabBarBuilder: buildTabBar, tabBarBuilder: _buildTabBar,
pageWrapperBuilder: _buildTabBarSelection,
builder: (_) => new TabsDemo() builder: (_) => new TabsDemo()
); );
...@@ -4,12 +4,22 @@ ...@@ -4,12 +4,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef Widget PageWrapperBuilder(BuildContext context, Widget child);
class WidgetDemo { class WidgetDemo {
WidgetDemo({ this.title, this.routeName, this.tabBarBuilder, this.floatingActionButtonBuilder, this.builder }); WidgetDemo({
this.title,
this.routeName,
this.tabBarBuilder,
this.pageWrapperBuilder,
this.floatingActionButtonBuilder,
this.builder
});
final String title; final String title;
final String routeName; final String routeName;
final WidgetBuilder tabBarBuilder; final WidgetBuilder tabBarBuilder;
final PageWrapperBuilder pageWrapperBuilder;
final WidgetBuilder floatingActionButtonBuilder; final WidgetBuilder floatingActionButtonBuilder;
final WidgetBuilder builder; final WidgetBuilder builder;
} }
...@@ -63,20 +63,27 @@ class _GalleryPageState extends State<GalleryPage> { ...@@ -63,20 +63,27 @@ class _GalleryPageState extends State<GalleryPage> {
return builder != null ? builder(context) : null; return builder != null ? builder(context) : null;
} }
Widget _buildPageWrapper(BuildContext context, Widget child) {
final PageWrapperBuilder builder = config.active?.pageWrapperBuilder;
return builder != null ? builder(context, child) : child;
}
Widget _buildFloatingActionButton() { Widget _buildFloatingActionButton() {
final WidgetBuilder builder = config.active?.floatingActionButtonBuilder; final WidgetBuilder builder = config.active?.floatingActionButtonBuilder;
return builder != null ? builder(context) : null; return builder != null ? builder(context) : null;
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return _buildPageWrapper(context,
toolBar: new ToolBar( new Scaffold(
center: new Text(config.active?.title ?? 'Flutter Material gallery'), toolBar: new ToolBar(
tabBar: _buildTabBar() center: new Text(config.active?.title ?? 'Flutter Material gallery'),
), tabBar: _buildTabBar()
drawer: _buildDrawer(), ),
floatingActionButton: _buildFloatingActionButton(), drawer: _buildDrawer(),
body: _buildBody() floatingActionButton: _buildFloatingActionButton(),
body: _buildBody()
)
); );
} }
} }
...@@ -24,15 +24,9 @@ class StockHomeState extends State<StockHome> { ...@@ -24,15 +24,9 @@ class StockHomeState extends State<StockHome> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool _isSearching = false; bool _isSearching = false;
String _searchQuery; String _searchQuery;
TabBarSelection _tabBarSelection;
void initState() { void initState() {
super.initState(); super.initState();
_tabBarSelection = PageStorage.of(context)?.readState(context);
if (_tabBarSelection == null) {
_tabBarSelection = new TabBarSelection(maxIndex: 1);
PageStorage.of(context)?.writeState(context, _tabBarSelection);
}
} }
void _handleSearchBegin() { void _handleSearchBegin() {
...@@ -168,7 +162,6 @@ class StockHomeState extends State<StockHome> { ...@@ -168,7 +162,6 @@ class StockHomeState extends State<StockHome> {
) )
], ],
tabBar: new TabBar( tabBar: new TabBar(
selection: _tabBarSelection,
labels: <TabLabel>[ labels: <TabLabel>[
new TabLabel(text: StockStrings.of(context).market()), new TabLabel(text: StockStrings.of(context).market()),
new TabLabel(text: StockStrings.of(context).portfolio()) new TabLabel(text: StockStrings.of(context).portfolio())
...@@ -273,24 +266,26 @@ class StockHomeState extends State<StockHome> { ...@@ -273,24 +266,26 @@ class StockHomeState extends State<StockHome> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new TabBarSelection(
key: _scaffoldKey, maxIndex: 1,
toolBar: _isSearching ? buildSearchBar() : buildToolBar(), child: new Scaffold(
floatingActionButton: buildFloatingActionButton(), key: _scaffoldKey,
drawer: _buildDrawer(context), toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
body: new TabBarView<StockHomeTab>( floatingActionButton: buildFloatingActionButton(),
selection: _tabBarSelection, drawer: _buildDrawer(context),
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio], body: new TabBarView<StockHomeTab>(
itemBuilder: (BuildContext context, StockHomeTab tab, _) { items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
switch (tab) { itemBuilder: (BuildContext context, StockHomeTab tab, _) {
case StockHomeTab.market: switch (tab) {
return _buildStockTab(context, tab, config.symbols); case StockHomeTab.market:
case StockHomeTab.portfolio: return _buildStockTab(context, tab, config.symbols);
return _buildStockTab(context, tab, portfolioSymbols); case StockHomeTab.portfolio:
default: return _buildStockTab(context, tab, portfolioSymbols);
assert(false); default:
assert(false);
}
} }
} )
) )
); );
} }
......
...@@ -381,32 +381,26 @@ class _TabsScrollBehavior extends BoundedBehavior { ...@@ -381,32 +381,26 @@ class _TabsScrollBehavior extends BoundedBehavior {
} }
} }
class _TabBarSelection extends InheritedWidget { abstract class TabBarSelectionPerformanceListener {
_TabBarSelection({ void handleStatusChange(PerformanceStatus status);
this.selection, void handleProgressChange();
Key key, void handleSelectionDeactivate();
Widget child
}) : super(key: key, child: child);
final TabBarSelectionState selection;
bool updateShouldNotify(_TabBarSelection oldWidget) => selection != oldWidget.selection;
} }
class TabBarSelection extends StatefulComponent { class TabBarSelection extends StatefulComponent {
TabBarSelection({ TabBarSelection({
Key key, Key key,
this.index: 0, this.index,
this.maxIndex, this.maxIndex,
this.onChanged, this.onChanged,
Widget this.child this.child
}) { }) : super(key: key) {
assert(child != null);
assert(maxIndex != null); assert(maxIndex != null);
assert(index != null); assert((index != null) ? index >= 0 && index <= maxIndex : true);
assert(index >= 0 && index <= maxIndex);
} }
final int index; // TBD: this doesn't work yet... final int index;
final int maxIndex; final int maxIndex;
final Widget child; final Widget child;
final ValueChanged<int> onChanged; final ValueChanged<int> onChanged;
...@@ -414,20 +408,25 @@ class TabBarSelection extends StatefulComponent { ...@@ -414,20 +408,25 @@ class TabBarSelection extends StatefulComponent {
TabBarSelectionState createState() => new TabBarSelectionState(); TabBarSelectionState createState() => new TabBarSelectionState();
static TabBarSelectionState of(BuildContext context) { static TabBarSelectionState of(BuildContext context) {
_TabBarSelection widget = context.inheritFromWidgetOfType(_TabBarSelection); return context.ancestorStateOfType(TabBarSelectionState);
return widget?.selection;
} }
} }
class TabBarSelectionState extends State<TabBarSelection> { class TabBarSelectionState extends State<TabBarSelection> {
PerformanceView get performance => _performance.view;
// Both the TabBar and TabBarView classes access _performance because they // Both the TabBar and TabBarView classes access _performance because they
// alternately drive selection progress between tabs. // alternately drive selection progress between tabs.
PerformanceView get performance => _performance.view;
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0); final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
void initState() {
super.initState();
_index = config.index ?? PageStorage.of(context)?.readState(context) ?? 0;
}
void dispose() { void dispose() {
_performance.stop(); _performance.stop();
PageStorage.of(context)?.writeState(context, _index);
super.dispose(); super.dispose();
} }
...@@ -435,11 +434,12 @@ class TabBarSelectionState extends State<TabBarSelection> { ...@@ -435,11 +434,12 @@ class TabBarSelectionState extends State<TabBarSelection> {
bool get indexIsChanging => _indexIsChanging; bool get indexIsChanging => _indexIsChanging;
int get index => _index; int get index => _index;
int _index = 0; int _index;
void set index(int value) { void set index(int value) {
if (value == _index) if (value == _index)
return; return;
_previousIndex = _index; if (!_indexIsChanging)
_previousIndex = _index;
_index = value; _index = value;
_indexIsChanging = true; _indexIsChanging = true;
...@@ -469,17 +469,43 @@ class TabBarSelectionState extends State<TabBarSelection> { ...@@ -469,17 +469,43 @@ class TabBarSelectionState extends State<TabBarSelection> {
_performance _performance
..progress = progress ..progress = progress
..forward().then((_) { ..forward().then((_) {
if (config.onChanged != null) if (_performance.progress == 1.0) {
config.onChanged(_index); if (config.onChanged != null)
_indexIsChanging = false; config.onChanged(_index);
_indexIsChanging = false;
}
}); });
} }
int get previousIndex => _previousIndex; int get previousIndex => _previousIndex;
int _previousIndex = 0; int _previousIndex = 0;
final List<TabBarSelectionPerformanceListener> _performanceListeners = <TabBarSelectionPerformanceListener>[];
void registerPerformanceListener(TabBarSelectionPerformanceListener listener) {
_performanceListeners.add(listener);
_performance
..addStatusListener(listener.handleStatusChange)
..addListener(listener.handleProgressChange);
}
void unregisterPerformanceListener(TabBarSelectionPerformanceListener listener) {
_performanceListeners.remove(listener);
_performance
..removeStatusListener(listener.handleStatusChange)
..removeListener(listener.handleProgressChange);
}
void deactivate() {
for (TabBarSelectionPerformanceListener listener in _performanceListeners.toList()) {
listener.handleSelectionDeactivate();
unregisterPerformanceListener(listener);
}
assert(_performanceListeners.isEmpty);
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new _TabBarSelection(selection: this, child: config.child); return config.child;
} }
} }
...@@ -507,40 +533,30 @@ class TabBar extends Scrollable { ...@@ -507,40 +533,30 @@ class TabBar extends Scrollable {
_TabBarState createState() => new _TabBarState(); _TabBarState createState() => new _TabBarState();
} }
class _TabBarState extends ScrollableState<TabBar> { class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPerformanceListener {
TabBarSelectionState _selection; TabBarSelectionState _selection;
bool _indexIsChanging = false; bool _indexIsChanging = false;
int get _tabCount => config.labels.length; int get _tabCount => config.labels.length;
void _addSelectionListeners(TabBarSelectionState selection) {
if (selection != null) {
selection._performance
..addStatusListener(_handleStatusChange)
..addListener(_handleProgressChange);
}
}
void _removeSelectionListeners(TabBarSelectionState selection) {
if (selection != null) {
selection._performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange);
}
}
void initState() { void initState() {
super.initState(); super.initState();
scrollBehavior.isScrollable = config.isScrollable; scrollBehavior.isScrollable = config.isScrollable;
_selection = TabBarSelection.of(context);
_selection?.registerPerformanceListener(this);
} }
void dispose() { void dispose() {
_removeSelectionListeners(_selection); _selection?.unregisterPerformanceListener(this);
super.dispose(); super.dispose();
} }
void _handleStatusChange(PerformanceStatus status) { void handleSelectionDeactivate() {
_selection = null;
}
void handleStatusChange(PerformanceStatus status) {
if (_tabCount == 0) if (_tabCount == 0)
return; return;
...@@ -561,7 +577,7 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -561,7 +577,7 @@ class _TabBarState extends ScrollableState<TabBar> {
} }
} }
void _handleProgressChange() { void handleProgressChange() {
if (_tabCount == 0 || _selection == null) if (_tabCount == 0 || _selection == null)
return; return;
...@@ -574,9 +590,11 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -574,9 +590,11 @@ class _TabBarState extends ScrollableState<TabBar> {
..curve = Curves.ease; ..curve = Curves.ease;
_indexIsChanging = true; _indexIsChanging = true;
} }
setState(() { Rect oldRect = _indicatorRect.value;
_indicatorRect.setProgress(_selection.performance.progress, AnimationDirection.forward); _indicatorRect.setProgress(_selection.performance.progress, AnimationDirection.forward);
}); Rect newRect = _indicatorRect.value;
if (oldRect != newRect)
setState(() { });
} }
Size _viewportSize = Size.zero; Size _viewportSize = Size.zero;
...@@ -671,8 +689,8 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -671,8 +689,8 @@ class _TabBarState extends ScrollableState<TabBar> {
TabBarSelectionState oldSelection = _selection; TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context); _selection = TabBarSelection.of(context);
if (oldSelection != _selection) { if (oldSelection != _selection) {
_removeSelectionListeners(oldSelection); oldSelection?.registerPerformanceListener(this);
_addSelectionListeners(_selection); _selection?.registerPerformanceListener(this);
} }
assert(config.labels != null && config.labels.isNotEmpty); assert(config.labels != null && config.labels.isNotEmpty);
...@@ -746,7 +764,7 @@ class TabBarView<T> extends PageableList<T> { ...@@ -746,7 +764,7 @@ class TabBarView<T> extends PageableList<T> {
_TabBarViewState createState() => new _TabBarViewState<T>(); _TabBarViewState createState() => new _TabBarViewState<T>();
} }
class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements TabBarSelectionPerformanceListener {
TabBarSelectionState _selection; TabBarSelectionState _selection;
List<int> _itemIndices = [0, 1]; List<int> _itemIndices = [0, 1];
...@@ -754,16 +772,6 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -754,16 +772,6 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
int get _tabCount => config.items.length; int get _tabCount => config.items.length;
void _addSelectionListeners(TabBarSelectionState selection) {
if (selection != null)
selection._performance.addListener(_handleProgressChange);
}
void _removeSelectionListeners(TabBarSelectionState selection) {
if (selection != null)
selection._performance.removeListener(_handleProgressChange);
}
BoundedBehavior _boundedBehavior; BoundedBehavior _boundedBehavior;
ExtentScrollBehavior get scrollBehavior { ExtentScrollBehavior get scrollBehavior {
...@@ -771,11 +779,25 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -771,11 +779,25 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
return _boundedBehavior; return _boundedBehavior;
} }
void initState() {
super.initState();
_selection = TabBarSelection.of(context);
if (_selection != null) {
_selection.registerPerformanceListener(this);
_initItemIndicesAndScrollPosition();
}
}
void dispose() { void dispose() {
_removeSelectionListeners(_selection); _selection?.unregisterPerformanceListener(this);
super.dispose(); super.dispose();
} }
void handleSelectionDeactivate() {
_selection = null;
}
void _initItemIndicesAndScrollPosition() { void _initItemIndicesAndScrollPosition() {
assert(_selection != null); assert(_selection != null);
final int selectedIndex = _selection.index; final int selectedIndex = _selection.index;
...@@ -791,7 +813,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -791,7 +813,10 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
} }
} }
void _handleProgressChange() { void handleStatusChange(PerformanceStatus status) {
}
void handleProgressChange() {
if (_selection == null || !_selection.indexIsChanging) if (_selection == null || !_selection.indexIsChanging)
return; return;
// The TabBar is driving the TabBarSelection performance. // The TabBar is driving the TabBarSelection performance.
...@@ -865,10 +890,8 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -865,10 +890,8 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
TabBarSelectionState oldSelection = _selection; TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context); _selection = TabBarSelection.of(context);
if (oldSelection != _selection) { if (oldSelection != _selection) {
_removeSelectionListeners(oldSelection); oldSelection?.unregisterPerformanceListener(this);
_addSelectionListeners(_selection); _selection?.registerPerformanceListener(this);
if (_selection != null)
_initItemIndicesAndScrollPosition();
} }
return _itemIndices return _itemIndices
......
...@@ -8,7 +8,6 @@ import 'constants.dart'; ...@@ -8,7 +8,6 @@ import 'constants.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
import 'material.dart'; import 'material.dart';
import 'tabs.dart';
import 'theme.dart'; import 'theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -30,7 +29,7 @@ class ToolBar extends StatelessComponent { ...@@ -30,7 +29,7 @@ class ToolBar extends StatelessComponent {
final Widget center; final Widget center;
final List<Widget> right; final List<Widget> right;
final Widget bottom; final Widget bottom;
final TabBar tabBar; final Widget tabBar;
final int elevation; final int elevation;
final Color backgroundColor; final Color backgroundColor;
final TextTheme textTheme; final TextTheme textTheme;
......
...@@ -7,14 +7,15 @@ import 'package:flutter/material.dart'; ...@@ -7,14 +7,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
TabBarSelection selection;
Widget buildFrame({ List<String> tabs, bool isScrollable: false }) { Widget buildFrame({ List<String> tabs, bool isScrollable: false }) {
return new Material( return new Material(
child: new TabBar( child: new TabBarSelection(
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(), index: 2,
selection: selection, maxIndex: tabs.length - 1,
isScrollable: isScrollable child: new TabBar(
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(),
isScrollable: isScrollable
)
) )
); );
} }
...@@ -23,9 +24,10 @@ void main() { ...@@ -23,9 +24,10 @@ void main() {
test('TabBar tap selects tab', () { test('TabBar tap selects tab', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C']; List<String> tabs = <String>['A', 'B', 'C'];
selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false)); tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
TabBarSelectionState selection = tester.findStateOfType(TabBarSelectionState);
expect(selection, isNotNull);
expect(tester.findText('A'), isNotNull); expect(tester.findText('A'), isNotNull);
expect(tester.findText('B'), isNotNull); expect(tester.findText('B'), isNotNull);
expect(tester.findText('C'), isNotNull); expect(tester.findText('C'), isNotNull);
...@@ -51,9 +53,10 @@ void main() { ...@@ -51,9 +53,10 @@ void main() {
test('Scrollable TabBar tap selects tab', () { test('Scrollable TabBar tap selects tab', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C']; List<String> tabs = <String>['A', 'B', 'C'];
selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true)); tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
TabBarSelectionState selection = tester.findStateOfType(TabBarSelectionState);
expect(selection, isNotNull);
expect(tester.findText('A'), isNotNull); expect(tester.findText('A'), isNotNull);
expect(tester.findText('B'), isNotNull); expect(tester.findText('B'), isNotNull);
expect(tester.findText('C'), isNotNull); expect(tester.findText('C'), isNotNull);
......
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