Merge pull request #1077 from HansMuller/selection_type
Convert TabBar to TabBar<T> and TabBarSelection to TabBarSelection<T> The TabBarSelection constructor no longer has the odd maxIndex parameter and the selection is initialized by value rather than the index of the selected tab. TabBar has a Map labels parameter instead of List.
This commit is contained in:
commit
fa15fc2d04
@ -9,16 +9,15 @@ 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"];
|
||||||
|
|
||||||
Widget _buildTabBarSelection(_, Widget child) {
|
Widget _buildTabBarSelection(_, Widget child) {
|
||||||
return new TabBarSelection(
|
return new TabBarSelection<String>(values: _iconNames, child: child);
|
||||||
maxIndex: _iconNames.length - 1,
|
|
||||||
child: child
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTabBar(_) {
|
Widget _buildTabBar(_) {
|
||||||
return new TabBar(
|
return new TabBar<String>(
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
labels: _iconNames.map((String iconName) => new TabLabel(text: iconName, icon: "action/$iconName")).toList()
|
labels: new Map.fromIterable(
|
||||||
|
_iconNames,
|
||||||
|
value: (String iconName) => new TabLabel(text: iconName, icon: "action/$iconName"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,17 +161,15 @@ class StockHomeState extends State<StockHome> {
|
|||||||
onPressed: _handleMenuShow
|
onPressed: _handleMenuShow
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
tabBar: new TabBar(
|
tabBar: new TabBar<StockHomeTab>(
|
||||||
labels: <TabLabel>[
|
labels: <StockHomeTab, TabLabel>{
|
||||||
new TabLabel(text: StockStrings.of(context).market()),
|
StockHomeTab.market: new TabLabel(text: StockStrings.of(context).market()),
|
||||||
new TabLabel(text: StockStrings.of(context).portfolio())
|
StockHomeTab.portfolio: new TabLabel(text: StockStrings.of(context).portfolio())
|
||||||
]
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int selectedTabIndex = 0;
|
|
||||||
|
|
||||||
Iterable<Stock> _getStockList(Iterable<String> symbols) {
|
Iterable<Stock> _getStockList(Iterable<String> symbols) {
|
||||||
return symbols.map((String symbol) => config.stocks[symbol])
|
return symbols.map((String symbol) => config.stocks[symbol])
|
||||||
.where((Stock stock) => stock != null);
|
.where((Stock stock) => stock != null);
|
||||||
@ -266,8 +264,8 @@ class StockHomeState extends State<StockHome> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new TabBarSelection(
|
return new TabBarSelection<StockHomeTab>(
|
||||||
maxIndex: 1,
|
values: <StockHomeTab>[StockHomeTab.market, StockHomeTab.portfolio],
|
||||||
child: new Scaffold(
|
child: new Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
|
toolBar: _isSearching ? buildSearchBar() : buildToolBar(),
|
||||||
|
@ -387,81 +387,115 @@ abstract class TabBarSelectionPerformanceListener {
|
|||||||
void handleSelectionDeactivate();
|
void handleSelectionDeactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabBarSelection extends StatefulComponent {
|
class TabBarSelection<T> extends StatefulComponent {
|
||||||
TabBarSelection({
|
TabBarSelection({
|
||||||
Key key,
|
Key key,
|
||||||
this.index,
|
this.value,
|
||||||
this.maxIndex,
|
this.values,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.child
|
this.child
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
|
assert(values != null && values.length > 0);
|
||||||
|
assert(new Set<T>.from(values).length == values.length);
|
||||||
|
assert(value == null ? true : values.where((T e) => e == value).length == 1);
|
||||||
assert(child != null);
|
assert(child != null);
|
||||||
assert(maxIndex != null);
|
|
||||||
assert((index != null) ? index >= 0 && index <= maxIndex : true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final int index;
|
final T value;
|
||||||
final int maxIndex;
|
List<T> values;
|
||||||
|
final ValueChanged<T> onChanged;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final ValueChanged<int> onChanged;
|
|
||||||
|
|
||||||
TabBarSelectionState createState() => new TabBarSelectionState();
|
TabBarSelectionState createState() => new TabBarSelectionState<T>();
|
||||||
|
|
||||||
static TabBarSelectionState of(BuildContext context) {
|
static TabBarSelectionState of(BuildContext context) {
|
||||||
return context.ancestorStateOfType(TabBarSelectionState);
|
TabBarSelectionState result = null;
|
||||||
|
context.visitAncestorElements((ancestor) {
|
||||||
|
if (ancestor is StatefulComponentElement && ancestor.state is TabBarSelectionState) {
|
||||||
|
result = ancestor.state;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabBarSelectionState extends State<TabBarSelection> {
|
class TabBarSelectionState<T> extends State<TabBarSelection<T>> {
|
||||||
|
|
||||||
PerformanceView get performance => _performance.view;
|
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.
|
||||||
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
|
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
|
||||||
|
final Map<T, int> _valueToIndex = new Map<T, int>();
|
||||||
|
|
||||||
|
void _initValueToIndex() {
|
||||||
|
_valueToIndex.clear();
|
||||||
|
int index = 0;
|
||||||
|
for(T value in values)
|
||||||
|
_valueToIndex[value] = index++;
|
||||||
|
}
|
||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_index = config.index ?? PageStorage.of(context)?.readState(context) ?? 0;
|
_value = config.value ?? PageStorage.of(context)?.readState(context) ?? values.first;
|
||||||
|
_previousValue = _value;
|
||||||
|
_initValueToIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void didUpdateConfig(TabBarSelection oldConfig) {
|
||||||
|
super.didUpdateConfig(oldConfig);
|
||||||
|
if (values != oldConfig.values)
|
||||||
|
_initValueToIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_performance.stop();
|
_performance.stop();
|
||||||
PageStorage.of(context)?.writeState(context, _index);
|
PageStorage.of(context)?.writeState(context, _value);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _indexIsChanging = false;
|
List<T> get values => config.values;
|
||||||
bool get indexIsChanging => _indexIsChanging;
|
|
||||||
|
|
||||||
int get index => _index;
|
T get previousValue => _previousValue;
|
||||||
int _index;
|
T _previousValue;
|
||||||
void set index(int value) {
|
|
||||||
if (value == _index)
|
bool _valueIsChanging = false;
|
||||||
|
bool get valueIsChanging => _valueIsChanging;
|
||||||
|
|
||||||
|
int indexOf(T tabValue) => _valueToIndex[tabValue];
|
||||||
|
int get index => _valueToIndex[value];
|
||||||
|
int get previousIndex => indexOf(_previousValue);
|
||||||
|
|
||||||
|
T get value => _value;
|
||||||
|
T _value;
|
||||||
|
void set value(T newValue) {
|
||||||
|
if (newValue == _value)
|
||||||
return;
|
return;
|
||||||
if (!_indexIsChanging)
|
if (!_valueIsChanging)
|
||||||
_previousIndex = _index;
|
_previousValue = _value;
|
||||||
_index = value;
|
_value = newValue;
|
||||||
_indexIsChanging = true;
|
_valueIsChanging = true;
|
||||||
|
|
||||||
// If the selected index change was triggered by a drag gesture, the current
|
// If the selected value change was triggered by a drag gesture, the current
|
||||||
// value of _performance.progress will reflect where the gesture ended. While
|
// value of _performance.progress will reflect where the gesture ended. While
|
||||||
// the drag was underway progress indicates where the indicator and TabBarView
|
// the drag was underway progress indicates where the indicator and TabBarView
|
||||||
// scrollPosition are vis the indices of the two tabs adjacent to the selected
|
// scrollPosition are vis the indices of the two tabs adjacent to the selected
|
||||||
// one. So 0.5 means the drag didn't move at all, 0.0 means the drag extended
|
// one. So 0.5 means the drag didn't move at all, 0.0 means the drag extended
|
||||||
// to the beginning of the tab on the left and 1.0 likewise for the tab on the
|
// to the beginning of the tab on the left and 1.0 likewise for the tab on the
|
||||||
// right. That is unless the selected index was 0 or maxIndex. In those cases
|
// right. That is unless the index of the selected value was 0 or values.length - 1.
|
||||||
// progress just moves between the selected tab and the adjacent one.
|
// In those cases progress just moves between the selected tab and the adjacent
|
||||||
// Convert progress to reflect the fact that we're now moving between (just)
|
// one. Convert progress to reflect the fact that we're now moving between (just)
|
||||||
// the previous and current selection index.
|
// the previous and current selection index.
|
||||||
|
|
||||||
double progress;
|
double progress;
|
||||||
if (_performance.status == PerformanceStatus.completed)
|
if (_performance.status == PerformanceStatus.completed)
|
||||||
progress = 0.0;
|
progress = 0.0;
|
||||||
else if (_previousIndex == 0)
|
else if (_previousValue == values.first)
|
||||||
progress = _performance.progress;
|
progress = _performance.progress;
|
||||||
else if (_previousIndex == config.maxIndex)
|
else if (_previousValue == values.last)
|
||||||
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;
|
||||||
else
|
else
|
||||||
progress = 1.0 - _performance.progress * 2.0;
|
progress = 1.0 - _performance.progress * 2.0;
|
||||||
@ -471,15 +505,12 @@ class TabBarSelectionState extends State<TabBarSelection> {
|
|||||||
..forward().then((_) {
|
..forward().then((_) {
|
||||||
if (_performance.progress == 1.0) {
|
if (_performance.progress == 1.0) {
|
||||||
if (config.onChanged != null)
|
if (config.onChanged != null)
|
||||||
config.onChanged(_index);
|
config.onChanged(_value);
|
||||||
_indexIsChanging = false;
|
_valueIsChanging = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int get previousIndex => _previousIndex;
|
|
||||||
int _previousIndex = 0;
|
|
||||||
|
|
||||||
final List<TabBarSelectionPerformanceListener> _performanceListeners = <TabBarSelectionPerformanceListener>[];
|
final List<TabBarSelectionPerformanceListener> _performanceListeners = <TabBarSelectionPerformanceListener>[];
|
||||||
|
|
||||||
void registerPerformanceListener(TabBarSelectionPerformanceListener listener) {
|
void registerPerformanceListener(TabBarSelectionPerformanceListener listener) {
|
||||||
@ -509,7 +540,6 @@ class TabBarSelectionState extends State<TabBarSelection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Displays a horizontal row of tabs, one per label. If isScrollable is
|
/// 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
|
/// 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
|
/// [TabBar] is scrollable. Otherwise each tab gets an equal share of the
|
||||||
@ -517,34 +547,40 @@ class TabBarSelectionState extends State<TabBarSelection> {
|
|||||||
/// built to enable saving and monitoring the selected tab.
|
/// 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<T> extends Scrollable {
|
||||||
TabBar({
|
TabBar({
|
||||||
Key key,
|
Key key,
|
||||||
this.labels,
|
this.labels,
|
||||||
this.isScrollable: false
|
this.isScrollable: false
|
||||||
}) : super(key: key, scrollDirection: ScrollDirection.horizontal) {
|
}) : super(key: key, scrollDirection: ScrollDirection.horizontal);
|
||||||
assert(labels != null);
|
|
||||||
assert(labels.length > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Iterable<TabLabel> labels;
|
final Map<T, TabLabel> labels;
|
||||||
final bool isScrollable;
|
final bool isScrollable;
|
||||||
|
|
||||||
_TabBarState createState() => new _TabBarState();
|
_TabBarState createState() => new _TabBarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPerformanceListener {
|
class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelectionPerformanceListener {
|
||||||
|
|
||||||
TabBarSelectionState _selection;
|
TabBarSelectionState _selection;
|
||||||
bool _indexIsChanging = false;
|
bool _valueIsChanging = false;
|
||||||
|
|
||||||
int get _tabCount => config.labels.length;
|
void _initSelection(TabBarSelectionState<T> selection) {
|
||||||
|
_selection?.unregisterPerformanceListener(this);
|
||||||
|
_selection = selection;
|
||||||
|
_selection?.registerPerformanceListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
scrollBehavior.isScrollable = config.isScrollable;
|
scrollBehavior.isScrollable = config.isScrollable;
|
||||||
_selection = TabBarSelection.of(context);
|
_initSelection(TabBarSelection.of(context));
|
||||||
_selection?.registerPerformanceListener(this);
|
}
|
||||||
|
|
||||||
|
void didUpdateConfig(TabBar oldConfig) {
|
||||||
|
super.didUpdateConfig(oldConfig);
|
||||||
|
if (!config.isScrollable)
|
||||||
|
scrollTo(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -557,20 +593,20 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleStatusChange(PerformanceStatus status) {
|
void handleStatusChange(PerformanceStatus status) {
|
||||||
if (_tabCount == 0)
|
if (config.labels.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_indexIsChanging && status == PerformanceStatus.completed) {
|
if (_valueIsChanging && status == PerformanceStatus.completed) {
|
||||||
_indexIsChanging = false;
|
_valueIsChanging = false;
|
||||||
double progress = 0.5;
|
double progress = 0.5;
|
||||||
if (_selection.index == 0)
|
if (_selection.index == 0)
|
||||||
progress = 0.0;
|
progress = 0.0;
|
||||||
else if (_selection.index == _tabCount - 1)
|
else if (_selection.index == config.labels.length - 1)
|
||||||
progress = 1.0;
|
progress = 1.0;
|
||||||
setState(() {
|
setState(() {
|
||||||
_indicatorRect
|
_indicatorRect
|
||||||
..begin = _tabIndicatorRect(math.max(0, _selection.index - 1))
|
..begin = _tabIndicatorRect(math.max(0, _selection.index - 1))
|
||||||
..end = _tabIndicatorRect(math.min(_tabCount - 1, _selection.index + 1))
|
..end = _tabIndicatorRect(math.min(config.labels.length - 1, _selection.index + 1))
|
||||||
..curve = null
|
..curve = null
|
||||||
..setProgress(progress, AnimationDirection.forward);
|
..setProgress(progress, AnimationDirection.forward);
|
||||||
});
|
});
|
||||||
@ -578,17 +614,17 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleProgressChange() {
|
void handleProgressChange() {
|
||||||
if (_tabCount == 0 || _selection == null)
|
if (config.labels.length == 0 || _selection == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_indexIsChanging && _selection.indexIsChanging) {
|
if (!_valueIsChanging && _selection.valueIsChanging) {
|
||||||
if (config.isScrollable)
|
if (config.isScrollable)
|
||||||
scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
|
scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
|
||||||
_indicatorRect
|
_indicatorRect
|
||||||
..begin = _indicatorRect.value ?? _tabIndicatorRect(_selection.previousIndex)
|
..begin = _indicatorRect.value ?? _tabIndicatorRect(_selection.previousIndex)
|
||||||
..end = _tabIndicatorRect(_selection.index)
|
..end = _tabIndicatorRect(_selection.index)
|
||||||
..curve = Curves.ease;
|
..curve = Curves.ease;
|
||||||
_indexIsChanging = true;
|
_valueIsChanging = true;
|
||||||
}
|
}
|
||||||
Rect oldRect = _indicatorRect.value;
|
Rect oldRect = _indicatorRect.value;
|
||||||
_indicatorRect.setProgress(_selection.performance.progress, AnimationDirection.forward);
|
_indicatorRect.setProgress(_selection.performance.progress, AnimationDirection.forward);
|
||||||
@ -620,12 +656,6 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
return new Rect.fromLTRB(r.left, r.bottom, r.right, r.bottom + _kTabIndicatorHeight);
|
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();
|
ScrollBehavior createScrollBehavior() => new _TabsScrollBehavior();
|
||||||
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
|
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||||
|
|
||||||
@ -639,7 +669,7 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
void _handleTabSelected(int tabIndex) {
|
void _handleTabSelected(int tabIndex) {
|
||||||
if (_selection != null && tabIndex != _selection.index)
|
if (_selection != null && tabIndex != _selection.index)
|
||||||
setState(() {
|
setState(() {
|
||||||
_selection.index = tabIndex;
|
_selection.value = _selection.values[tabIndex];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,7 +679,7 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
final bool isSelectedTab = tabIndex == _selection.index;
|
final bool isSelectedTab = tabIndex == _selection.index;
|
||||||
final bool isPreviouslySelectedTab = tabIndex == _selection.previousIndex;
|
final bool isPreviouslySelectedTab = tabIndex == _selection.previousIndex;
|
||||||
labelColor = isSelectedTab ? selectedColor : color;
|
labelColor = isSelectedTab ? selectedColor : color;
|
||||||
if (_selection.indexIsChanging) {
|
if (_selection.valueIsChanging) {
|
||||||
if (isSelectedTab)
|
if (isSelectedTab)
|
||||||
labelColor = Color.lerp(color, selectedColor, _selection.performance.progress);
|
labelColor = Color.lerp(color, selectedColor, _selection.performance.progress);
|
||||||
else if (isPreviouslySelectedTab)
|
else if (isPreviouslySelectedTab)
|
||||||
@ -686,14 +716,11 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildContent(BuildContext context) {
|
Widget buildContent(BuildContext context) {
|
||||||
TabBarSelectionState oldSelection = _selection;
|
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
|
||||||
_selection = TabBarSelection.of(context);
|
if (_selection != newSelection)
|
||||||
if (oldSelection != _selection) {
|
_initSelection(newSelection);
|
||||||
oldSelection?.registerPerformanceListener(this);
|
|
||||||
_selection?.registerPerformanceListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(config.labels != null && config.labels.isNotEmpty);
|
assert(config.labels.isNotEmpty);
|
||||||
assert(Material.of(context) != null);
|
assert(Material.of(context) != null);
|
||||||
|
|
||||||
ThemeData themeData = Theme.of(context);
|
ThemeData themeData = Theme.of(context);
|
||||||
@ -708,7 +735,7 @@ class _TabBarState extends ScrollableState<TabBar> implements TabBarSelectionPer
|
|||||||
List<Widget> tabs = <Widget>[];
|
List<Widget> tabs = <Widget>[];
|
||||||
bool textAndIcons = false;
|
bool textAndIcons = false;
|
||||||
int tabIndex = 0;
|
int tabIndex = 0;
|
||||||
for (TabLabel label in config.labels) {
|
for (TabLabel label in config.labels.values) {
|
||||||
tabs.add(_toTab(label, tabIndex++, textStyle.color, indicatorColor));
|
tabs.add(_toTab(label, tabIndex++, textStyle.color, indicatorColor));
|
||||||
if (label.text != null && label.icon != null)
|
if (label.text != null && label.icon != null)
|
||||||
textAndIcons = true;
|
textAndIcons = true;
|
||||||
@ -780,15 +807,19 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void initState() {
|
void _initSelection(TabBarSelectionState<T> selection) {
|
||||||
super.initState();
|
_selection = selection;
|
||||||
_selection = TabBarSelection.of(context);
|
|
||||||
if (_selection != null) {
|
if (_selection != null) {
|
||||||
_selection.registerPerformanceListener(this);
|
_selection.registerPerformanceListener(this);
|
||||||
_initItemIndicesAndScrollPosition();
|
_initItemIndicesAndScrollPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initSelection(TabBarSelection.of(context));
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_selection?.unregisterPerformanceListener(this);
|
_selection?.unregisterPerformanceListener(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -817,7 +848,7 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleProgressChange() {
|
void handleProgressChange() {
|
||||||
if (_selection == null || !_selection.indexIsChanging)
|
if (_selection == null || !_selection.valueIsChanging)
|
||||||
return;
|
return;
|
||||||
// The TabBar is driving the TabBarSelection performance.
|
// The TabBar is driving the TabBarSelection performance.
|
||||||
|
|
||||||
@ -851,7 +882,7 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
|||||||
int get itemCount => _itemIndices.length;
|
int get itemCount => _itemIndices.length;
|
||||||
|
|
||||||
void dispatchOnScroll() {
|
void dispatchOnScroll() {
|
||||||
if (_selection == null || _selection.indexIsChanging)
|
if (_selection == null || _selection.valueIsChanging)
|
||||||
return;
|
return;
|
||||||
// This class is driving the TabBarSelection's performance.
|
// This class is driving the TabBarSelection's performance.
|
||||||
|
|
||||||
@ -864,36 +895,31 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future fling(Offset scrollVelocity) {
|
Future fling(Offset scrollVelocity) {
|
||||||
// TODO(hansmuller): should not short-circuit in this case.
|
if (_selection == null || _selection.valueIsChanging)
|
||||||
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;
|
||||||
_selection.index = (_selection.index + selectionDelta).clamp(0, _tabCount - 1);
|
_selection.value = _selection.values[(_selection.index + selectionDelta).clamp(0, _tabCount - 1)];
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
final int selectionIndex = _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) {
|
||||||
_selection.index += settleIndex == 2 ? 1 : -1;
|
_selection.value = _selection.values[selectionIndex + (settleIndex == 2 ? 1 : -1)];
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
} else if (selectionIndex == 0 && settleIndex == 1) {
|
} else if (selectionIndex == 0 && settleIndex == 1) {
|
||||||
_selection.index = 1;
|
_selection.value = _selection.values[1];
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
}
|
}
|
||||||
return settleScrollOffset();
|
return settleScrollOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||||
TabBarSelectionState oldSelection = _selection;
|
TabBarSelectionState<T> newSelection = TabBarSelection.of(context);
|
||||||
_selection = TabBarSelection.of(context);
|
if (_selection != newSelection)
|
||||||
if (oldSelection != _selection) {
|
_initSelection(newSelection);
|
||||||
oldSelection?.unregisterPerformanceListener(this);
|
|
||||||
_selection?.registerPerformanceListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _itemIndices
|
return _itemIndices
|
||||||
.skip(start)
|
.skip(start)
|
||||||
.take(count)
|
.take(count)
|
||||||
|
@ -7,13 +7,13 @@ 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';
|
||||||
|
|
||||||
Widget buildFrame({ List<String> tabs, bool isScrollable: false }) {
|
Widget buildFrame({ List<String> tabs, String value, bool isScrollable: false }) {
|
||||||
return new Material(
|
return new Material(
|
||||||
child: new TabBarSelection(
|
child: new TabBarSelection<String>(
|
||||||
index: 2,
|
value: value,
|
||||||
maxIndex: tabs.length - 1,
|
values: tabs,
|
||||||
child: new TabBar(
|
child: new TabBar<String>(
|
||||||
labels: tabs.map((String tab) => new TabLabel(text: tab)).toList(),
|
labels: new Map<String, TabLabel>.fromIterable(tabs, value: (String tab) => new TabLabel(text: tab)),
|
||||||
isScrollable: isScrollable
|
isScrollable: isScrollable
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -25,28 +25,48 @@ void main() {
|
|||||||
testWidgets((WidgetTester tester) {
|
testWidgets((WidgetTester tester) {
|
||||||
List<String> tabs = <String>['A', 'B', 'C'];
|
List<String> tabs = <String>['A', 'B', 'C'];
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: false));
|
||||||
TabBarSelectionState selection = tester.findStateOfType(TabBarSelectionState);
|
TabBarSelectionState<String> selection = TabBarSelection.of(tester.findText('A'));
|
||||||
expect(selection, isNotNull);
|
expect(selection, isNotNull);
|
||||||
|
expect(selection.indexOf('A'), equals(0));
|
||||||
|
expect(selection.indexOf('B'), equals(1));
|
||||||
|
expect(selection.indexOf('C'), equals(2));
|
||||||
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);
|
||||||
expect(selection.index, equals(2));
|
expect(selection.index, equals(2));
|
||||||
|
expect(selection.previousIndex, equals(2));
|
||||||
|
expect(selection.value, equals('C'));
|
||||||
|
expect(selection.previousValue, equals('C'));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C' ,isScrollable: false));
|
||||||
tester.tap(tester.findText('B'));
|
tester.tap(tester.findText('B'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
|
expect(selection.valueIsChanging, true);
|
||||||
|
tester.pump(const Duration(seconds: 1)); // finish the animation
|
||||||
|
expect(selection.valueIsChanging, false);
|
||||||
|
expect(selection.value, equals('B'));
|
||||||
|
expect(selection.previousValue, equals('C'));
|
||||||
expect(selection.index, equals(1));
|
expect(selection.index, equals(1));
|
||||||
|
expect(selection.previousIndex, equals(2));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: false));
|
||||||
tester.tap(tester.findText('C'));
|
tester.tap(tester.findText('C'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
|
tester.pump(const Duration(seconds: 1));
|
||||||
|
expect(selection.value, equals('C'));
|
||||||
|
expect(selection.previousValue, equals('B'));
|
||||||
expect(selection.index, equals(2));
|
expect(selection.index, equals(2));
|
||||||
|
expect(selection.previousIndex, equals(1));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: false));
|
||||||
tester.tap(tester.findText('A'));
|
tester.tap(tester.findText('A'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
|
tester.pump(const Duration(seconds: 1));
|
||||||
|
expect(selection.value, equals('A'));
|
||||||
|
expect(selection.previousValue, equals('C'));
|
||||||
expect(selection.index, equals(0));
|
expect(selection.index, equals(0));
|
||||||
|
expect(selection.previousIndex, equals(2));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,28 +74,28 @@ void main() {
|
|||||||
testWidgets((WidgetTester tester) {
|
testWidgets((WidgetTester tester) {
|
||||||
List<String> tabs = <String>['A', 'B', 'C'];
|
List<String> tabs = <String>['A', 'B', 'C'];
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: true));
|
||||||
TabBarSelectionState selection = tester.findStateOfType(TabBarSelectionState);
|
TabBarSelectionState<String> selection = TabBarSelection.of(tester.findText('A'));
|
||||||
expect(selection, isNotNull);
|
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);
|
||||||
expect(selection.index, equals(2));
|
expect(selection.value, equals('C'));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: true));
|
||||||
tester.tap(tester.findText('B'));
|
tester.tap(tester.findText('B'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
expect(selection.index, equals(1));
|
expect(selection.value, equals('B'));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: true));
|
||||||
tester.tap(tester.findText('C'));
|
tester.tap(tester.findText('C'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
expect(selection.index, equals(2));
|
expect(selection.value, equals('C'));
|
||||||
|
|
||||||
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
|
tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', isScrollable: true));
|
||||||
tester.tap(tester.findText('A'));
|
tester.tap(tester.findText('A'));
|
||||||
tester.pump();
|
tester.pump();
|
||||||
expect(selection.index, equals(0));
|
expect(selection.value, equals('A'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user