Commit fd4599ff authored by Hans Muller's avatar Hans Muller

Merge pull request #969 from HansMuller/fix_tab_swipe_animation

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.
parents bf751921 65dca5b5
...@@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; ...@@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'widget_demo.dart'; import 'widget_demo.dart';
final TabBarSelection _selection = new TabBarSelection();
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 buildTabBar(_) {
return new TabBar( return new TabBar(
......
...@@ -30,7 +30,7 @@ class StockHomeState extends State<StockHome> { ...@@ -30,7 +30,7 @@ class StockHomeState extends State<StockHome> {
super.initState(); super.initState();
_tabBarSelection = PageStorage.of(context)?.readState(context); _tabBarSelection = PageStorage.of(context)?.readState(context);
if (_tabBarSelection == null) { if (_tabBarSelection == null) {
_tabBarSelection = new TabBarSelection(); _tabBarSelection = new TabBarSelection(maxIndex: 1);
PageStorage.of(context)?.writeState(context, _tabBarSelection); PageStorage.of(context)?.writeState(context, _tabBarSelection);
} }
} }
......
...@@ -382,9 +382,14 @@ class _TabsScrollBehavior extends BoundedBehavior { ...@@ -382,9 +382,14 @@ class _TabsScrollBehavior extends BoundedBehavior {
} }
class TabBarSelection { 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 VoidCallback onChanged;
final int maxIndex;
PerformanceView get performance => _performance.view; PerformanceView get performance => _performance.view;
final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0); final _performance = new Performance(duration: _kTabBarScroll, progress: 1.0);
...@@ -400,9 +405,33 @@ class TabBarSelection { ...@@ -400,9 +405,33 @@ class TabBarSelection {
_previousIndex = _index; _previousIndex = _index;
_index = value; _index = value;
_indexIsChanging = true; _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 _performance
..progress = 0.0 ..progress = progress
..play().then((_) { ..forward().then((_) {
if (onChanged != null) if (onChanged != null)
onChanged(); onChanged();
_indexIsChanging = false; _indexIsChanging = false;
...@@ -425,7 +454,9 @@ class TabBar extends Scrollable { ...@@ -425,7 +454,9 @@ class TabBar extends Scrollable {
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(selection != null); assert(selection != null);
assert(selection.maxIndex == labels.length - 1);
} }
final Iterable<TabLabel> labels; final Iterable<TabLabel> labels;
...@@ -648,7 +679,10 @@ class TabBarView<T> extends PageableList<T> { ...@@ -648,7 +679,10 @@ class TabBarView<T> extends PageableList<T> {
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
itemsWrap: false itemsWrap: false
) { ) {
assert(items != null);
assert(items.length > 1);
assert(selection != null); assert(selection != null);
assert(selection.maxIndex == items.length - 1);
} }
final TabBarSelection selection; final TabBarSelection selection;
...@@ -690,43 +724,39 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> { ...@@ -690,43 +724,39 @@ class _TabBarViewState<T> extends PageableListState<T, TabBarView<T>> {
super.initState(); super.initState();
_initItemIndicesAndScrollPosition(); _initItemIndicesAndScrollPosition();
_performance _performance
..addStatusListener(_handleStatusChange)
..addListener(_handleProgressChange); ..addListener(_handleProgressChange);
} }
void dispose() { void dispose() {
_performance _performance
..removeStatusListener(_handleStatusChange)
..removeListener(_handleProgressChange) ..removeListener(_handleProgressChange)
..stop(); ..stop();
super.dispose(); super.dispose();
} }
void _handleStatusChange(PerformanceStatus status) { void _handleProgressChange() {
if (!config.selection.indexIsChanging) if (!config.selection.indexIsChanging)
return; return;
// The TabBar is driving the TabBarSelection performance. // The TabBar is driving the TabBarSelection performance.
final int selectedIndex = config.selection.index; if (_performance.status == PerformanceStatus.completed) {
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) {
_initItemIndicesAndScrollPosition(); _initItemIndicesAndScrollPosition();
return;
} }
}
void _handleProgressChange() { if (_performance.status != PerformanceStatus.forward)
if (!config.selection.indexIsChanging)
return; 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) if (_scrollDirection == AnimationDirection.forward)
scrollTo(_performance.progress); scrollTo(_performance.progress);
......
...@@ -23,7 +23,7 @@ void main() { ...@@ -23,7 +23,7 @@ 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); selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false)); tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: false));
expect(tester.findText('A'), isNotNull); expect(tester.findText('A'), isNotNull);
...@@ -51,7 +51,7 @@ void main() { ...@@ -51,7 +51,7 @@ 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); selection = new TabBarSelection(index: 2, maxIndex: tabs.length - 1);
tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true)); tester.pumpWidget(buildFrame(tabs: tabs, isScrollable: true));
expect(tester.findText('A'), isNotNull); 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