@@ -1529,10 +1529,10 @@ class TabBarView extends StatefulWidget {
1529
1529
class _TabBarViewState extends State <TabBarView > {
1530
1530
TabController ? _controller;
1531
1531
late PageController _pageController;
1532
- late List <Widget > _children;
1533
1532
late List <Widget > _childrenWithKey;
1534
1533
int ? _currentIndex;
1535
1534
int _warpUnderwayCount = 0 ;
1535
+ int _scrollUnderwayCount = 0 ;
1536
1536
bool _debugHasScheduledValidChildrenCountCheck = false ;
1537
1537
1538
1538
// If the TabBarView is rebuilt with a new tab controller, the caller should
@@ -1568,6 +1568,22 @@ class _TabBarViewState extends State<TabBarView> {
1568
1568
}
1569
1569
}
1570
1570
1571
+ void _jumpToPage (int page) {
1572
+ _warpUnderwayCount += 1 ;
1573
+ _pageController.jumpToPage (page);
1574
+ _warpUnderwayCount -= 1 ;
1575
+ }
1576
+
1577
+ Future <void > _animateToPage (
1578
+ int page, {
1579
+ required Duration duration,
1580
+ required Curve curve,
1581
+ }) async {
1582
+ _warpUnderwayCount += 1 ;
1583
+ await _pageController.animateToPage (page, duration: duration, curve: curve);
1584
+ _warpUnderwayCount -= 1 ;
1585
+ }
1586
+
1571
1587
@override
1572
1588
void initState () {
1573
1589
super .initState ();
@@ -1591,10 +1607,10 @@ class _TabBarViewState extends State<TabBarView> {
1591
1607
if (widget.controller != oldWidget.controller) {
1592
1608
_updateTabController ();
1593
1609
_currentIndex = _controller! .index;
1594
- _warpUnderwayCount += 1 ;
1595
- _pageController.jumpToPage (_currentIndex! );
1596
- _warpUnderwayCount -= 1 ;
1610
+ _jumpToPage (_currentIndex! );
1597
1611
}
1612
+ // While a warp is under way, we stop updating the tab page contents.
1613
+ // This is tracked in https://github.com/flutter/flutter/issues/31269.
1598
1614
if (widget.children != oldWidget.children && _warpUnderwayCount == 0 ) {
1599
1615
_updateChildren ();
1600
1616
}
@@ -1611,12 +1627,11 @@ class _TabBarViewState extends State<TabBarView> {
1611
1627
}
1612
1628
1613
1629
void _updateChildren () {
1614
- _children = widget.children;
1615
1630
_childrenWithKey = KeyedSubtree .ensureUniqueKeysForList (widget.children);
1616
1631
}
1617
1632
1618
1633
void _handleTabControllerAnimationTick () {
1619
- if (_warpUnderwayCount > 0 || ! _controller! .indexIsChanging) {
1634
+ if (_scrollUnderwayCount > 0 || ! _controller! .indexIsChanging) {
1620
1635
return ;
1621
1636
} // This widget is driving the controller's animation.
1622
1637
@@ -1626,93 +1641,96 @@ class _TabBarViewState extends State<TabBarView> {
1626
1641
}
1627
1642
}
1628
1643
1629
- Future < void > _warpToCurrentIndex () async {
1630
- if (! mounted) {
1631
- return Future < void >. value () ;
1644
+ void _warpToCurrentIndex () {
1645
+ if (! mounted || _pageController.page == _currentIndex ! . toDouble () ) {
1646
+ return ;
1632
1647
}
1633
1648
1634
- if (_pageController.page == _currentIndex! .toDouble ()) {
1635
- return Future <void >.value ();
1649
+ final bool adjacentDestination = (_currentIndex! - _controller! .previousIndex).abs () == 1 ;
1650
+ if (adjacentDestination) {
1651
+ _warpToAdjacentTab (_controller! .animationDuration);
1652
+ } else {
1653
+ _warpToNonAdjacentTab (_controller! .animationDuration);
1636
1654
}
1655
+ }
1637
1656
1638
- final Duration duration = _controller! .animationDuration;
1639
- final int previousIndex = _controller! .previousIndex;
1640
-
1641
- if ((_currentIndex! - previousIndex).abs () == 1 ) {
1642
- if (duration == Duration .zero) {
1643
- _pageController.jumpToPage (_currentIndex! );
1644
- return Future <void >.value ();
1645
- }
1646
- _warpUnderwayCount += 1 ;
1647
- await _pageController.animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1648
- _warpUnderwayCount -= 1 ;
1649
-
1650
- if (mounted && widget.children != _children) {
1651
- setState (() { _updateChildren (); });
1652
- }
1653
- return Future <void >.value ();
1657
+ Future <void > _warpToAdjacentTab (Duration duration) async {
1658
+ if (duration == Duration .zero) {
1659
+ _jumpToPage (_currentIndex! );
1660
+ } else {
1661
+ await _animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1654
1662
}
1663
+ if (mounted) {
1664
+ setState (() { _updateChildren (); });
1665
+ }
1666
+ return Future <void >.value ();
1667
+ }
1655
1668
1669
+ Future <void > _warpToNonAdjacentTab (Duration duration) async {
1670
+ final int previousIndex = _controller! .previousIndex;
1656
1671
assert ((_currentIndex! - previousIndex).abs () > 1 );
1672
+
1673
+ // initialPage defines which page is shown when starting the animation.
1674
+ // This page is adjacent to the destination page.
1657
1675
final int initialPage = _currentIndex! > previousIndex
1658
1676
? _currentIndex! - 1
1659
1677
: _currentIndex! + 1 ;
1660
- final List <Widget > originalChildren = _childrenWithKey;
1661
- setState (() {
1662
- _warpUnderwayCount += 1 ;
1663
1678
1679
+ setState (() {
1680
+ // Needed for `RenderSliverMultiBoxAdaptor.move` and kept alive children.
1681
+ // For motivation, see https://github.com/flutter/flutter/pull/29188 and
1682
+ // https://github.com/flutter/flutter/issues/27010#issuecomment-486475152.
1664
1683
_childrenWithKey = List <Widget >.of (_childrenWithKey, growable: false );
1665
1684
final Widget temp = _childrenWithKey[initialPage];
1666
1685
_childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
1667
1686
_childrenWithKey[previousIndex] = temp;
1668
1687
});
1669
- _pageController.jumpToPage (initialPage);
1670
1688
1689
+ // Make a first jump to the adjacent page.
1690
+ _jumpToPage (initialPage);
1691
+
1692
+ // Jump or animate to the destination page.
1671
1693
if (duration == Duration .zero) {
1672
- _pageController. jumpToPage (_currentIndex! );
1694
+ _jumpToPage (_currentIndex! );
1673
1695
} else {
1674
- await _pageController.animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1696
+ await _animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1697
+ }
1675
1698
1676
- if (! mounted) {
1677
- return Future <void >.value ();
1678
- }
1699
+ if (mounted) {
1700
+ setState (() { _updateChildren (); });
1679
1701
}
1702
+ }
1680
1703
1681
- setState (() {
1682
- _warpUnderwayCount -= 1 ;
1683
- if (widget.children != _children) {
1684
- _updateChildren ();
1685
- } else {
1686
- _childrenWithKey = originalChildren;
1687
- }
1688
- });
1704
+ void _syncControllerOffset () {
1705
+ _controller! .offset = clampDouble (_pageController.page! - _controller! .index, - 1.0 , 1.0 );
1689
1706
}
1690
1707
1691
1708
// Called when the PageView scrolls
1692
1709
bool _handleScrollNotification (ScrollNotification notification) {
1693
- if (_warpUnderwayCount > 0 ) {
1710
+ if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0 ) {
1694
1711
return false ;
1695
1712
}
1696
1713
1697
1714
if (notification.depth != 0 ) {
1698
1715
return false ;
1699
1716
}
1700
1717
1701
- _warpUnderwayCount += 1 ;
1718
+ _scrollUnderwayCount += 1 ;
1702
1719
if (notification is ScrollUpdateNotification && ! _controller! .indexIsChanging) {
1703
- if ((_pageController.page! - _controller! .index).abs () > 1.0 ) {
1720
+ final bool pageChanged = (_pageController.page! - _controller! .index).abs () > 1.0 ;
1721
+ if (pageChanged) {
1704
1722
_controller! .index = _pageController.page! .round ();
1705
1723
_currentIndex = _controller! .index;
1706
1724
}
1707
- _controller ! .offset = clampDouble (_pageController.page ! - _controller ! .index, - 1.0 , 1.0 );
1725
+ _syncControllerOffset ( );
1708
1726
} else if (notification is ScrollEndNotification ) {
1709
1727
_controller! .index = _pageController.page! .round ();
1710
1728
_currentIndex = _controller! .index;
1711
1729
if (! _controller! .indexIsChanging) {
1712
- _controller ! .offset = clampDouble (_pageController.page ! - _controller ! .index, - 1.0 , 1.0 );
1730
+ _syncControllerOffset ( );
1713
1731
}
1714
1732
}
1715
- _warpUnderwayCount -= 1 ;
1733
+ _scrollUnderwayCount -= 1 ;
1716
1734
1717
1735
return false ;
1718
1736
}
0 commit comments