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