Commit a9ddbb4e authored by Hans Muller's avatar Hans Muller

Merge pull request #1007 from HansMuller/revise_tabbar_selection

Make TabBarSelection a widget

TabBarSelection is now expected to be an ancestor of its TabBar and TabBarView.
parents 95b50915 6178fdff
...@@ -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,13 +63,19 @@ class _GalleryPageState extends State<GalleryPage> { ...@@ -63,13 +63,19 @@ 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,
new Scaffold(
toolBar: new ToolBar( toolBar: new ToolBar(
center: new Text(config.active?.title ?? 'Flutter Material gallery'), center: new Text(config.active?.title ?? 'Flutter Material gallery'),
tabBar: _buildTabBar() tabBar: _buildTabBar()
...@@ -77,6 +83,7 @@ class _GalleryPageState extends State<GalleryPage> { ...@@ -77,6 +83,7 @@ class _GalleryPageState extends State<GalleryPage> {
drawer: _buildDrawer(), drawer: _buildDrawer(),
floatingActionButton: _buildFloatingActionButton(), floatingActionButton: _buildFloatingActionButton(),
body: _buildBody() 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,13 +266,14 @@ class StockHomeState extends State<StockHome> { ...@@ -273,13 +266,14 @@ class StockHomeState extends State<StockHome> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new TabBarSelection(
maxIndex: 1,
child: new Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
toolBar: _isSearching ? buildSearchBar() : buildToolBar(), toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
floatingActionButton: buildFloatingActionButton(), floatingActionButton: buildFloatingActionButton(),
drawer: _buildDrawer(context), drawer: _buildDrawer(context),
body: new TabBarView<StockHomeTab>( body: new TabBarView<StockHomeTab>(
selection: _tabBarSelection,
items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio], items: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
itemBuilder: (BuildContext context, StockHomeTab tab, _) { itemBuilder: (BuildContext context, StockHomeTab tab, _) {
switch (tab) { switch (tab) {
...@@ -292,6 +286,7 @@ class StockHomeState extends State<StockHome> { ...@@ -292,6 +286,7 @@ class StockHomeState extends State<StockHome> {
} }
} }
) )
)
); );
} }
} }
...@@ -381,19 +381,55 @@ class _TabsScrollBehavior extends BoundedBehavior { ...@@ -381,19 +381,55 @@ class _TabsScrollBehavior extends BoundedBehavior {
} }
} }
class TabBarSelection { abstract class TabBarSelectionPerformanceListener {
TabBarSelection({ int index: 0, this.maxIndex, this.onChanged }) : _index = index { void handleStatusChange(PerformanceStatus status);
void handleProgressChange();
void handleSelectionDeactivate();
}
class TabBarSelection extends StatefulComponent {
TabBarSelection({
Key key,
this.index,
this.maxIndex,
this.onChanged,
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 VoidCallback onChanged; final int index;
final int maxIndex; final int maxIndex;
final Widget child;
final ValueChanged<int> onChanged;
TabBarSelectionState createState() => new TabBarSelectionState();
static TabBarSelectionState of(BuildContext context) {
return context.ancestorStateOfType(TabBarSelectionState);
}
}
class TabBarSelectionState extends State<TabBarSelection> {
PerformanceView get performance => _performance.view; PerformanceView get performance => _performance.view;
// Both the TabBar and TabBarView classes access _performance because they
// alternately drive selection progress between tabs.
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() {
_performance.stop();
PageStorage.of(context)?.writeState(context, _index);
super.dispose();
}
bool _indexIsChanging = false; bool _indexIsChanging = false;
bool get indexIsChanging => _indexIsChanging; bool get indexIsChanging => _indexIsChanging;
...@@ -402,6 +438,7 @@ class TabBarSelection { ...@@ -402,6 +438,7 @@ class TabBarSelection {
void set index(int value) { void set index(int value) {
if (value == _index) if (value == _index)
return; return;
if (!_indexIsChanging)
_previousIndex = _index; _previousIndex = _index;
_index = value; _index = value;
_indexIsChanging = true; _indexIsChanging = true;
...@@ -422,7 +459,7 @@ class TabBarSelection { ...@@ -422,7 +459,7 @@ class TabBarSelection {
progress = 0.0; progress = 0.0;
else if (_previousIndex == 0) else if (_previousIndex == 0)
progress = _performance.progress; progress = _performance.progress;
else if (_previousIndex == maxIndex) else if (_previousIndex == config.maxIndex)
progress = 1.0 - _performance.progress; progress = 1.0 - _performance.progress;
else if (_previousIndex < _index) else if (_previousIndex < _index)
progress = (_performance.progress - 0.5) * 2.0; progress = (_performance.progress - 0.5) * 2.0;
...@@ -432,100 +469,132 @@ class TabBarSelection { ...@@ -432,100 +469,132 @@ class TabBarSelection {
_performance _performance
..progress = progress ..progress = progress
..forward().then((_) { ..forward().then((_) {
if (onChanged != null) if (_performance.progress == 1.0) {
onChanged(); if (config.onChanged != null)
config.onChanged(_index);
_indexIsChanging = false; _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) {
return config.child;
}
} }
/// A tab strip, consisting of several TabLabels and a TabBarSelection.
/// The TabBarSelection can be used to link this to a TabBarView. /// Displays a horizontal row of tabs, one per label. If isScrollable is
/// true then each tab is as wide as needed for its label and the entire
/// [TabBar] is scrollable. Otherwise each tab gets an equal share of the
/// available space. A [TabBarSelection] widget ancestor must have been
/// built to enable saving and monitoring the selected tab.
/// ///
/// Tabs must always have an ancestor Material object. /// Tabs must always have an ancestor Material object.
class TabBar extends Scrollable { class TabBar extends Scrollable {
TabBar({ TabBar({
Key key, Key key,
this.labels, this.labels,
this.selection,
this.isScrollable: false this.isScrollable: false
}) : super(key: key, scrollDirection: ScrollDirection.horizontal) { }) : super(key: key, scrollDirection: ScrollDirection.horizontal) {
assert(labels != null); assert(labels != null);
assert(labels.length > 1); assert(labels.length > 1);
assert(selection != null);
assert(selection.maxIndex == labels.length - 1);
} }
final Iterable<TabLabel> labels; final Iterable<TabLabel> labels;
final TabBarSelection selection;
final bool isScrollable; final bool isScrollable;
_TabBarState createState() => new _TabBarState(); _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 initState() { void initState() {
super.initState(); super.initState();
scrollBehavior.isScrollable = config.isScrollable; scrollBehavior.isScrollable = config.isScrollable;
config.selection._performance _selection = TabBarSelection.of(context);
..addStatusListener(_handleStatusChange) _selection?.registerPerformanceListener(this);
..addListener(_handleProgressChange);
} }
void dispose() { void dispose() {
config.selection._performance _selection?.unregisterPerformanceListener(this);
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange)
..stop();
super.dispose(); super.dispose();
} }
Performance get _performance => config.selection._performance; void handleSelectionDeactivate() {
_selection = null;
int get _tabCount => config.labels.length; }
bool _indexIsChanging = false;
void _handleStatusChange(PerformanceStatus status) { void handleStatusChange(PerformanceStatus status) {
if (_tabCount == 0) if (_tabCount == 0)
return; return;
if (_indexIsChanging && status == PerformanceStatus.completed) { if (_indexIsChanging && status == PerformanceStatus.completed) {
_indexIsChanging = false; _indexIsChanging = false;
double progress = 0.5; double progress = 0.5;
if (config.selection.index == 0) if (_selection.index == 0)
progress = 0.0; progress = 0.0;
else if (config.selection.index == _tabCount - 1) else if (_selection.index == _tabCount - 1)
progress = 1.0; progress = 1.0;
setState(() { setState(() {
_indicatorRect _indicatorRect
..begin = _tabIndicatorRect(math.max(0, config.selection.index - 1)) ..begin = _tabIndicatorRect(math.max(0, _selection.index - 1))
..end = _tabIndicatorRect(math.min(_tabCount - 1, config.selection.index + 1)) ..end = _tabIndicatorRect(math.min(_tabCount - 1, _selection.index + 1))
..curve = null ..curve = null
..setProgress(progress, AnimationDirection.forward); ..setProgress(progress, AnimationDirection.forward);
}); });
} }
} }
void _handleProgressChange() { void handleProgressChange() {
if (_tabCount == 0) if (_tabCount == 0 || _selection == null)
return; return;
if (!_indexIsChanging && config.selection.indexIsChanging) { if (!_indexIsChanging && _selection.indexIsChanging) {
if (config.isScrollable) if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll); scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
_indicatorRect _indicatorRect
..begin = _indicatorRect.value ?? _tabIndicatorRect(config.selection.previousIndex) ..begin = _indicatorRect.value ?? _tabIndicatorRect(_selection.previousIndex)
..end = _tabIndicatorRect(config.selection.index) ..end = _tabIndicatorRect(_selection.index)
..curve = Curves.ease; ..curve = Curves.ease;
_indexIsChanging = true; _indexIsChanging = true;
} }
setState(() { Rect oldRect = _indicatorRect.value;
_indicatorRect.setProgress(_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;
...@@ -568,21 +637,24 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -568,21 +637,24 @@ class _TabBarState extends ScrollableState<TabBar> {
} }
void _handleTabSelected(int tabIndex) { void _handleTabSelected(int tabIndex) {
if (tabIndex != config.selection.index) if (_selection != null && tabIndex != _selection.index)
setState(() { setState(() {
config.selection.index = tabIndex; _selection.index = tabIndex;
}); });
} }
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) { Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
final bool isSelectedTab = tabIndex == config.selection.index; Color labelColor = color;
final bool isPreviouslySelectedTab = tabIndex == config.selection.previousIndex; if (_selection != null) {
Color labelColor = isSelectedTab ? selectedColor : color; final bool isSelectedTab = tabIndex == _selection.index;
if (config.selection.indexIsChanging) { final bool isPreviouslySelectedTab = tabIndex == _selection.previousIndex;
labelColor = isSelectedTab ? selectedColor : color;
if (_selection.indexIsChanging) {
if (isSelectedTab) if (isSelectedTab)
labelColor = Color.lerp(color, selectedColor, _performance.progress); labelColor = Color.lerp(color, selectedColor, _selection.performance.progress);
else if (isPreviouslySelectedTab) else if (isPreviouslySelectedTab)
labelColor = Color.lerp(selectedColor, color, _performance.progress); labelColor = Color.lerp(selectedColor, color, _selection.performance.progress);
}
} }
return new _Tab( return new _Tab(
onSelected: () { _handleTabSelected(tabIndex); }, onSelected: () { _handleTabSelected(tabIndex); },
...@@ -610,10 +682,17 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -610,10 +682,17 @@ class _TabBarState extends ScrollableState<TabBar> {
_viewportSize = newSize; _viewportSize = newSize;
_updateScrollBehavior(); _updateScrollBehavior();
if (config.isScrollable) if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll); scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
} }
Widget buildContent(BuildContext context) { Widget buildContent(BuildContext context) {
TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context);
if (oldSelection != _selection) {
oldSelection?.registerPerformanceListener(this);
_selection?.registerPerformanceListener(this);
}
assert(config.labels != null && config.labels.isNotEmpty); assert(config.labels != null && config.labels.isNotEmpty);
assert(Material.of(context) != null); assert(Material.of(context) != null);
...@@ -641,7 +720,7 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -641,7 +720,7 @@ class _TabBarState extends ScrollableState<TabBar> {
style: textStyle, style: textStyle,
child: new _TabBarWrapper( child: new _TabBarWrapper(
children: tabs, children: tabs,
selectedIndex: config.selection.index, selectedIndex: _selection?.index,
indicatorColor: indicatorColor, indicatorColor: indicatorColor,
indicatorRect: _indicatorRect.value, indicatorRect: _indicatorRect.value,
textAndIcons: textAndIcons, textAndIcons: textAndIcons,
...@@ -669,7 +748,6 @@ class _TabBarState extends ScrollableState<TabBar> { ...@@ -669,7 +748,6 @@ class _TabBarState extends ScrollableState<TabBar> {
class TabBarView<T> extends PageableList<T> { class TabBarView<T> extends PageableList<T> {
TabBarView({ TabBarView({
Key key, Key key,
this.selection,
List<T> items, List<T> items,
ItemBuilder<T> itemBuilder ItemBuilder<T> itemBuilder
}) : super( }) : super(
...@@ -681,36 +759,19 @@ class TabBarView<T> extends PageableList<T> { ...@@ -681,36 +759,19 @@ class TabBarView<T> extends PageableList<T> {
) { ) {
assert(items != null); assert(items != null);
assert(items.length > 1); assert(items.length > 1);
assert(selection != null);
assert(selection.maxIndex == items.length - 1);
} }
final TabBarSelection selection;
_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;
List<int> _itemIndices = [0, 1]; List<int> _itemIndices = [0, 1];
AnimationDirection _scrollDirection = AnimationDirection.forward; AnimationDirection _scrollDirection = AnimationDirection.forward;
int get _tabCount => config.items.length; int get _tabCount => config.items.length;
void _initItemIndicesAndScrollPosition() {
final int selectedIndex = config.selection.index;
if (selectedIndex == 0) {
_itemIndices = <int>[0, 1];
scrollTo(0.0);
} else if (selectedIndex == _tabCount - 1) {
_itemIndices = <int>[selectedIndex - 1, selectedIndex];
scrollTo(1.0);
} else {
_itemIndices = <int>[selectedIndex - 1, selectedIndex, selectedIndex + 1];
scrollTo(1.0);
}
}
BoundedBehavior _boundedBehavior; BoundedBehavior _boundedBehavior;
ExtentScrollBehavior get scrollBehavior { ExtentScrollBehavior get scrollBehavior {
...@@ -718,37 +779,60 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -718,37 +779,60 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
return _boundedBehavior; return _boundedBehavior;
} }
Performance get _performance => config.selection._performance;
void initState() { void initState() {
super.initState(); super.initState();
_selection = TabBarSelection.of(context);
if (_selection != null) {
_selection.registerPerformanceListener(this);
_initItemIndicesAndScrollPosition(); _initItemIndicesAndScrollPosition();
_performance }
..addListener(_handleProgressChange);
} }
void dispose() { void dispose() {
_performance _selection?.unregisterPerformanceListener(this);
..removeListener(_handleProgressChange)
..stop();
super.dispose(); super.dispose();
} }
void _handleProgressChange() { void handleSelectionDeactivate() {
if (!config.selection.indexIsChanging) _selection = null;
}
void _initItemIndicesAndScrollPosition() {
assert(_selection != null);
final int selectedIndex = _selection.index;
if (selectedIndex == 0) {
_itemIndices = <int>[0, 1];
scrollTo(0.0);
} else if (selectedIndex == _tabCount - 1) {
_itemIndices = <int>[selectedIndex - 1, selectedIndex];
scrollTo(1.0);
} else {
_itemIndices = <int>[selectedIndex - 1, selectedIndex, selectedIndex + 1];
scrollTo(1.0);
}
}
void handleStatusChange(PerformanceStatus status) {
}
void handleProgressChange() {
if (_selection == null || !_selection.indexIsChanging)
return; return;
// The TabBar is driving the TabBarSelection performance. // The TabBar is driving the TabBarSelection performance.
if (_performance.status == PerformanceStatus.completed) { final Performance performance = _selection.performance;
if (performance.status == PerformanceStatus.completed) {
_initItemIndicesAndScrollPosition(); _initItemIndicesAndScrollPosition();
return; return;
} }
if (_performance.status != PerformanceStatus.forward) if (performance.status != PerformanceStatus.forward)
return; return;
final int selectedIndex = config.selection.index; final int selectedIndex = _selection.index;
final int previousSelectedIndex = config.selection.previousIndex; final int previousSelectedIndex = _selection.previousIndex;
if (selectedIndex < previousSelectedIndex) { if (selectedIndex < previousSelectedIndex) {
_itemIndices = <int>[selectedIndex, previousSelectedIndex]; _itemIndices = <int>[selectedIndex, previousSelectedIndex];
...@@ -759,53 +843,61 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -759,53 +843,61 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
} }
if (_scrollDirection == AnimationDirection.forward) if (_scrollDirection == AnimationDirection.forward)
scrollTo(_performance.progress); scrollTo(performance.progress);
else else
scrollTo(1.0 - _performance.progress); scrollTo(1.0 - performance.progress);
} }
int get itemCount => _itemIndices.length; 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();
}
void dispatchOnScroll() { void dispatchOnScroll() {
if (config.selection.indexIsChanging) if (_selection == null || _selection.indexIsChanging)
return; return;
// This class is driving the TabBarSelection's performance. // This class is driving the TabBarSelection's performance.
if (config.selection.index == 0 || config.selection.index == _tabCount - 1) final Performance performance = _selection._performance;
_performance.progress = scrollOffset;
if (_selection.index == 0 || _selection.index == _tabCount - 1)
performance.progress = scrollOffset;
else else
_performance.progress = scrollOffset / 2.0; performance.progress = scrollOffset / 2.0;
} }
Future fling(Offset scrollVelocity) { Future fling(Offset scrollVelocity) {
// TODO(hansmuller): should not short-circuit in this case. // TODO(hansmuller): should not short-circuit in this case.
if (config.selection.indexIsChanging) if (_selection == null || _selection.indexIsChanging)
return new Future.value(); return new Future.value();
if (scrollVelocity.dx.abs() > _kMinFlingVelocity) { if (scrollVelocity.dx.abs() > _kMinFlingVelocity) {
final int selectionDelta = scrollVelocity.dx > 0 ? -1 : 1; final int selectionDelta = scrollVelocity.dx > 0 ? -1 : 1;
config.selection.index = (config.selection.index + selectionDelta).clamp(0, _tabCount - 1); _selection.index = (_selection.index + selectionDelta).clamp(0, _tabCount - 1);
return new Future.value(); return new Future.value();
} }
final int selectionIndex = config.selection.index; final int selectionIndex = _selection.index;
final int settleIndex = snapScrollOffset(scrollOffset).toInt(); final int settleIndex = snapScrollOffset(scrollOffset).toInt();
if (selectionIndex > 0 && settleIndex != 1) { if (selectionIndex > 0 && settleIndex != 1) {
config.selection.index += settleIndex == 2 ? 1 : -1; _selection.index += settleIndex == 2 ? 1 : -1;
return new Future.value(); return new Future.value();
} else if (selectionIndex == 0 && settleIndex == 1) { } else if (selectionIndex == 0 && settleIndex == 1) {
config.selection.index = 1; _selection.index = 1;
return new Future.value(); return new Future.value();
} }
return settleScrollOffset(); return settleScrollOffset();
} }
List<Widget> buildItems(BuildContext context, int start, int count) {
TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context);
if (oldSelection != _selection) {
oldSelection?.unregisterPerformanceListener(this);
_selection?.registerPerformanceListener(this);
}
return _itemIndices
.skip(start)
.take(count)
.map((int i) => config.itemBuilder(context, config.items[i], i))
.toList();
}
} }
...@@ -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,15 +7,16 @@ import 'package:flutter/material.dart'; ...@@ -7,15 +7,16 @@ 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 TabBarSelection(
index: 2,
maxIndex: tabs.length - 1,
child: new TabBar( child: new TabBar(
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(), labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(),
selection: selection,
isScrollable: isScrollable 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