Commit 65dca5b5 authored by Hans Muller's avatar Hans Muller

Correct the TabBarView swipe selection change animation

The TabBarSelection change animation needs to start where the fling's drag gesture ended rather than from zero.  The intial vlaue of progress for the TabBarSelection's performance is now converted from the range used during an interactive drag, to the range used when animating from the previously selected tab to the new one.

TabBarSelection now requires a maxIndex parameter.
parent 1e888693
......@@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'widget_demo.dart';
final TabBarSelection _selection = new TabBarSelection();
final List<String> _iconNames = <String>["event", "home", "android", "alarm", "face", "language"];
final TabBarSelection _selection = new TabBarSelection(maxIndex: _iconNames.length - 1);
Widget buildTabBar(_) {
return new TabBar(
......
......@@ -30,7 +30,7 @@ class StockHomeState extends State<StockHome> {
super.initState();
_tabBarSelection = PageStorage.of(context)?.readState(context);
if (_tabBarSelection == null) {
_tabBarSelection = new TabBarSelection();
_tabBarSelection = new TabBarSelection(maxIndex: 1);
PageStorage.of(context)?.writeState(context, _tabBarSelection);
}
}
......
......@@ -382,9 +382,14 @@ class _TabsScrollBehavior extends BoundedBehavior {
}
class TabBarSelection {
TabBarSelection({ int index: 0, this.onChanged }) : _index = index;
TabBarSelection({ int index: 0, this.maxIndex, this.onChanged }) : _index = index {
assert(maxIndex != null);
assert(index != null);
assert(_index >= 0 && _index <= maxIndex);
}
final VoidCallback onChanged;
final int maxIndex;
PerformanceView get performance => _performance.view;
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
......@@ -400,9 +405,33 @@ class TabBarSelection {
_previousIndex = _index;
_index = value;
_indexIsChanging = true;
// If the selected index change was triggered by a drag gesture, the current
// value of _performance.progress will reflect where the gesture ended. While
// the drag was underway progress indicates where the indicator and TabBarView
// 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
// 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
// progress just moves between the selected tab and the adjacent one.
// Convert progress to reflect the fact that we're now moving between (just)
// the previous and current selection index.
double progress;
if (_performance.status == PerformanceStatus.completed)
progress = 0.0;
else if (_previousIndex == 0)
progress = _performance.progress;
else if (_previousIndex == maxIndex)
progress = 1.0 - _performance.progress;
else if (_previousIndex < _index)
progress = (_performance.progress - 0.5) * 2.0;
else
progress = 1.0 - _performance.progress * 2.0;
_performance
..progress = 0.0
..play().then((_) {
..progress = progress
..forward().then((_) {
if (onChanged != null)
onChanged();
_indexIsChanging = false;
......@@ -425,7 +454,9 @@ class TabBar extends Scrollable {
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;
......@@ -648,7 +679,10 @@ class TabBarView<T> extends PageableList<T> {
itemBuilder: itemBuilder,
itemsWrap: false
) {
assert(items != null);
assert(items.length > 1);
assert(selection != null);
assert(selection.maxIndex == items.length - 1);
}
final TabBarSelection selection;
......@@ -690,43 +724,39 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
super.initState();
_initItemIndicesAndScrollPosition();
_performance
..addStatusListener(_handleStatusChange)
..addListener(_handleProgressChange);
}
void dispose() {
_performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange)
..stop();
super.dispose();
}
void _handleStatusChange(PerformanceStatus status) {
void _handleProgressChange() {
if (!config.selection.indexIsChanging)
return;
// The TabBar is driving the TabBarSelection performance.
final int selectedIndex = config.selection.index;
final int previousSelectedIndex = config.selection.previousIndex;
if (status == PerformanceStatus.forward) {
if (selectedIndex < previousSelectedIndex) {
_itemIndices = <int>[selectedIndex, previousSelectedIndex];
_scrollDirection = AnimationDirection.reverse;
} else {
_itemIndices = <int>[previousSelectedIndex, selectedIndex];
_scrollDirection = AnimationDirection.forward;
}
} else if (status == PerformanceStatus.completed) {
if (_performance.status == PerformanceStatus.completed) {
_initItemIndicesAndScrollPosition();
return;
}
}
void _handleProgressChange() {
if (!config.selection.indexIsChanging)
if (_performance.status != PerformanceStatus.forward)
return;
// The TabBar is driving the TabBarSelection performance.
final int selectedIndex = config.selection.index;
final int previousSelectedIndex = config.selection.previousIndex;
if (selectedIndex < previousSelectedIndex) {
_itemIndices = <int>[selectedIndex, previousSelectedIndex];
_scrollDirection = AnimationDirection.reverse;
} else {
_itemIndices = <int>[previousSelectedIndex, selectedIndex];
_scrollDirection = AnimationDirection.forward;
}
if (_scrollDirection == AnimationDirection.forward)
scrollTo(_performance.progress);
......
......@@ -23,7 +23,7 @@ void main() {
test('TabBar tap selects tab', () {
testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C'];
selection = new TabBarSelection(index: 2);
selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
expect(tester.findText('A'), isNotNull);
......@@ -51,7 +51,7 @@ void main() {
test('Scrollable TabBar tap selects tab', () {
testWidgets((WidgetTester tester) {
List<String> tabs = <String>['A', 'B', 'C'];
selection = new TabBarSelection(index: 2);
selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
expect(tester.findText('A'), 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