Commit 3f32201c authored by Hans Muller's avatar Hans Muller

checkpoint

parent 95b50915
......@@ -381,24 +381,61 @@ class _TabsScrollBehavior extends BoundedBehavior {
}
}
class TabBarSelection {
TabBarSelection({ int index: 0, this.maxIndex, this.onChanged }) : _index = index {
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;
}
class TabBarSelection extends StatefulComponent {
TabBarSelection({
Key key,
this.index: 0,
this.maxIndex,
this.onChanged,
Widget this.child
}) {
assert(maxIndex != null);
assert(index != null);
assert(_index >= 0 && _index <= maxIndex);
assert(index >= 0 && index <= maxIndex);
}
final VoidCallback onChanged;
final int index; // TBD: this doesn't work yet...
final int maxIndex;
final Widget child;
final ValueChanged<int> onChanged;
TabBarSelectionState createState() => new TabBarSelectionState();
static TabBarSelectionState of(BuildContext context) {
_TabBarSelection widget = context.inheritFromWidgetOfType(_TabBarSelection);
return widget?.selection;
}
}
class TabBarSelectionState extends State<TabBarSelection> {
// 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 dispose() {
_performance.stop();
super.dispose();
}
bool _indexIsChanging = false;
bool get indexIsChanging => _indexIsChanging;
int get index => _index;
int _index;
int _index = 0;
void set index(int value) {
if (value == _index)
return;
......@@ -422,7 +459,7 @@ class TabBarSelection {
progress = 0.0;
else if (_previousIndex == 0)
progress = _performance.progress;
else if (_previousIndex == maxIndex)
else if (_previousIndex == config.maxIndex)
progress = 1.0 - _performance.progress;
else if (_previousIndex < _index)
progress = (_performance.progress - 0.5) * 2.0;
......@@ -432,62 +469,76 @@ class TabBarSelection {
_performance
..progress = progress
..forward().then((_) {
if (onChanged != null)
onChanged();
if (config.onChanged != null)
config.onChanged(_index);
_indexIsChanging = false;
});
}
int get previousIndex => _previousIndex;
int _previousIndex = 0;
Widget build(BuildContext context) {
return new _TabBarSelection(selection: this, child: 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.
class TabBar extends Scrollable {
TabBar({
Key key,
this.labels,
this.selection,
this.isScrollable: false
}) : super(key: key, scrollDirection: ScrollDirection.horizontal) {
assert(labels != null);
assert(labels.length > 1);
assert(selection != null);
assert(selection.maxIndex == labels.length - 1);
}
final Iterable<TabLabel> labels;
final TabBarSelection selection;
final bool isScrollable;
_TabBarState createState() => new _TabBarState();
}
class _TabBarState extends ScrollableState<TabBar> {
void initState() {
super.initState();
scrollBehavior.isScrollable = config.isScrollable;
config.selection._performance
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 dispose() {
config.selection._performance
void _removeSelectionListeners(TabBarSelectionState selection) {
if (selection != null) {
selection._performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange)
..stop();
super.dispose();
..removeListener(_handleProgressChange);
}
}
Performance get _performance => config.selection._performance;
int get _tabCount => config.labels.length;
void initState() {
super.initState();
scrollBehavior.isScrollable = config.isScrollable;
}
bool _indexIsChanging = false;
void dispose() {
_removeSelectionListeners(_selection);
super.dispose();
}
void _handleStatusChange(PerformanceStatus status) {
if (_tabCount == 0)
......@@ -496,14 +547,14 @@ class _TabBarState extends ScrollableState<TabBar> {
if (_indexIsChanging && status == PerformanceStatus.completed) {
_indexIsChanging = false;
double progress = 0.5;
if (config.selection.index == 0)
if (_selection.index == 0)
progress = 0.0;
else if (config.selection.index == _tabCount - 1)
else if (_selection.index == _tabCount - 1)
progress = 1.0;
setState(() {
_indicatorRect
..begin = _tabIndicatorRect(math.max(0, config.selection.index - 1))
..end = _tabIndicatorRect(math.min(_tabCount - 1, config.selection.index + 1))
..begin = _tabIndicatorRect(math.max(0, _selection.index - 1))
..end = _tabIndicatorRect(math.min(_tabCount - 1, _selection.index + 1))
..curve = null
..setProgress(progress, AnimationDirection.forward);
});
......@@ -511,20 +562,20 @@ class _TabBarState extends ScrollableState<TabBar> {
}
void _handleProgressChange() {
if (_tabCount == 0)
if (_tabCount == 0 || _selection == null)
return;
if (!_indexIsChanging && config.selection.indexIsChanging) {
if (!_indexIsChanging && _selection.indexIsChanging) {
if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll);
scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
_indicatorRect
..begin = _indicatorRect.value ?? _tabIndicatorRect(config.selection.previousIndex)
..end = _tabIndicatorRect(config.selection.index)
..begin = _indicatorRect.value ?? _tabIndicatorRect(_selection.previousIndex)
..end = _tabIndicatorRect(_selection.index)
..curve = Curves.ease;
_indexIsChanging = true;
}
setState(() {
_indicatorRect.setProgress(_performance.progress, AnimationDirection.forward);
_indicatorRect.setProgress(_selection.performance.progress, AnimationDirection.forward);
});
}
......@@ -568,21 +619,24 @@ class _TabBarState extends ScrollableState<TabBar> {
}
void _handleTabSelected(int tabIndex) {
if (tabIndex != config.selection.index)
if (_selection != null && tabIndex != _selection.index)
setState(() {
config.selection.index = tabIndex;
_selection.index = tabIndex;
});
}
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
final bool isSelectedTab = tabIndex == config.selection.index;
final bool isPreviouslySelectedTab = tabIndex == config.selection.previousIndex;
Color labelColor = isSelectedTab ? selectedColor : color;
if (config.selection.indexIsChanging) {
Color labelColor = color;
if (_selection != null) {
final bool isSelectedTab = tabIndex == _selection.index;
final bool isPreviouslySelectedTab = tabIndex == _selection.previousIndex;
labelColor = isSelectedTab ? selectedColor : color;
if (_selection.indexIsChanging) {
if (isSelectedTab)
labelColor = Color.lerp(color, selectedColor, _performance.progress);
labelColor = Color.lerp(color, selectedColor, _selection.performance.progress);
else if (isPreviouslySelectedTab)
labelColor = Color.lerp(selectedColor, color, _performance.progress);
labelColor = Color.lerp(selectedColor, color, _selection.performance.progress);
}
}
return new _Tab(
onSelected: () { _handleTabSelected(tabIndex); },
......@@ -610,10 +664,17 @@ class _TabBarState extends ScrollableState<TabBar> {
_viewportSize = newSize;
_updateScrollBehavior();
if (config.isScrollable)
scrollTo(_centeredTabScrollOffset(config.selection.index), duration: _kTabBarScroll);
scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll);
}
Widget buildContent(BuildContext context) {
TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context);
if (oldSelection != _selection) {
_removeSelectionListeners(oldSelection);
_addSelectionListeners(_selection);
}
assert(config.labels != null && config.labels.isNotEmpty);
assert(Material.of(context) != null);
......@@ -641,7 +702,7 @@ class _TabBarState extends ScrollableState<TabBar> {
style: textStyle,
child: new _TabBarWrapper(
children: tabs,
selectedIndex: config.selection.index,
selectedIndex: _selection?.index,
indicatorColor: indicatorColor,
indicatorRect: _indicatorRect.value,
textAndIcons: textAndIcons,
......@@ -669,7 +730,6 @@ class _TabBarState extends ScrollableState<TabBar> {
class TabBarView<T> extends PageableList<T> {
TabBarView({
Key key,
this.selection,
List<T> items,
ItemBuilder<T> itemBuilder
}) : super(
......@@ -681,34 +741,27 @@ class TabBarView<T> extends PageableList<T> {
) {
assert(items != null);
assert(items.length > 1);
assert(selection != null);
assert(selection.maxIndex == items.length - 1);
}
final TabBarSelection selection;
_TabBarViewState createState() => new _TabBarViewState<T>();
}
class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
TabBarSelectionState _selection;
List<int> _itemIndices = [0, 1];
AnimationDirection _scrollDirection = AnimationDirection.forward;
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);
void _addSelectionListeners(TabBarSelectionState selection) {
if (selection != null)
selection._performance.addListener(_handleProgressChange);
}
void _removeSelectionListeners(TabBarSelectionState selection) {
if (selection != null)
selection._performance.removeListener(_handleProgressChange);
}
BoundedBehavior _boundedBehavior;
......@@ -718,37 +771,43 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
return _boundedBehavior;
}
Performance get _performance => config.selection._performance;
void initState() {
super.initState();
_initItemIndicesAndScrollPosition();
_performance
..addListener(_handleProgressChange);
}
void dispose() {
_performance
..removeListener(_handleProgressChange)
..stop();
_removeSelectionListeners(_selection);
super.dispose();
}
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 _handleProgressChange() {
if (!config.selection.indexIsChanging)
if (_selection == null || !_selection.indexIsChanging)
return;
// The TabBar is driving the TabBarSelection performance.
if (_performance.status == PerformanceStatus.completed) {
final Performance performance = _selection.performance;
if (performance.status == PerformanceStatus.completed) {
_initItemIndicesAndScrollPosition();
return;
}
if (_performance.status != PerformanceStatus.forward)
if (performance.status != PerformanceStatus.forward)
return;
final int selectedIndex = config.selection.index;
final int previousSelectedIndex = config.selection.previousIndex;
final int selectedIndex = _selection.index;
final int previousSelectedIndex = _selection.previousIndex;
if (selectedIndex < previousSelectedIndex) {
_itemIndices = <int>[selectedIndex, previousSelectedIndex];
......@@ -759,53 +818,63 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
}
if (_scrollDirection == AnimationDirection.forward)
scrollTo(_performance.progress);
scrollTo(performance.progress);
else
scrollTo(1.0 - _performance.progress);
scrollTo(1.0 - performance.progress);
}
int get itemCount => _itemIndices.length;
List<Widget> buildItems(BuildContext context, int start, int count) {
return _itemIndices
.skip(start)
.take(count)
.map((int i) => config.itemBuilder(context, config.items[i], i))
.toList();
}
void dispatchOnScroll() {
if (config.selection.indexIsChanging)
if (_selection == null || _selection.indexIsChanging)
return;
// This class is driving the TabBarSelection's performance.
if (config.selection.index == 0 || config.selection.index == _tabCount - 1)
_performance.progress = scrollOffset;
final Performance performance = _selection._performance;
if (_selection.index == 0 || _selection.index == _tabCount - 1)
performance.progress = scrollOffset;
else
_performance.progress = scrollOffset / 2.0;
performance.progress = scrollOffset / 2.0;
}
Future fling(Offset scrollVelocity) {
// TODO(hansmuller): should not short-circuit in this case.
if (config.selection.indexIsChanging)
if (_selection == null || _selection.indexIsChanging)
return new Future.value();
if (scrollVelocity.dx.abs() > _kMinFlingVelocity) {
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();
}
final int selectionIndex = config.selection.index;
final int selectionIndex = _selection.index;
final int settleIndex = snapScrollOffset(scrollOffset).toInt();
if (selectionIndex > 0 && settleIndex != 1) {
config.selection.index += settleIndex == 2 ? 1 : -1;
_selection.index += settleIndex == 2 ? 1 : -1;
return new Future.value();
} else if (selectionIndex == 0 && settleIndex == 1) {
config.selection.index = 1;
_selection.index = 1;
return new Future.value();
}
return settleScrollOffset();
}
List<Widget> buildItems(BuildContext context, int start, int count) {
TabBarSelectionState oldSelection = _selection;
_selection = TabBarSelection.of(context);
if (oldSelection != _selection) {
_removeSelectionListeners(oldSelection);
_addSelectionListeners(_selection);
if (_selection != null)
_initItemIndicesAndScrollPosition();
}
return _itemIndices
.skip(start)
.take(count)
.map((int i) => config.itemBuilder(context, config.items[i], i))
.toList();
}
}
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