@@ -1529,10 +1529,10 @@ class TabBarView extends StatefulWidget {
15291529class _TabBarViewState extends State <TabBarView > {
15301530 TabController ? _controller;
15311531 late PageController _pageController;
1532- late List <Widget > _children;
15331532 late List <Widget > _childrenWithKey;
15341533 int ? _currentIndex;
15351534 int _warpUnderwayCount = 0 ;
1535+ int _scrollUnderwayCount = 0 ;
15361536 bool _debugHasScheduledValidChildrenCountCheck = false ;
15371537
15381538 // If the TabBarView is rebuilt with a new tab controller, the caller should
@@ -1568,6 +1568,22 @@ class _TabBarViewState extends State<TabBarView> {
15681568 }
15691569 }
15701570
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+
15711587 @override
15721588 void initState () {
15731589 super .initState ();
@@ -1591,10 +1607,10 @@ class _TabBarViewState extends State<TabBarView> {
15911607 if (widget.controller != oldWidget.controller) {
15921608 _updateTabController ();
15931609 _currentIndex = _controller! .index;
1594- _warpUnderwayCount += 1 ;
1595- _pageController.jumpToPage (_currentIndex! );
1596- _warpUnderwayCount -= 1 ;
1610+ _jumpToPage (_currentIndex! );
15971611 }
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.
15981614 if (widget.children != oldWidget.children && _warpUnderwayCount == 0 ) {
15991615 _updateChildren ();
16001616 }
@@ -1611,12 +1627,11 @@ class _TabBarViewState extends State<TabBarView> {
16111627 }
16121628
16131629 void _updateChildren () {
1614- _children = widget.children;
16151630 _childrenWithKey = KeyedSubtree .ensureUniqueKeysForList (widget.children);
16161631 }
16171632
16181633 void _handleTabControllerAnimationTick () {
1619- if (_warpUnderwayCount > 0 || ! _controller! .indexIsChanging) {
1634+ if (_scrollUnderwayCount > 0 || ! _controller! .indexIsChanging) {
16201635 return ;
16211636 } // This widget is driving the controller's animation.
16221637
@@ -1626,93 +1641,96 @@ class _TabBarViewState extends State<TabBarView> {
16261641 }
16271642 }
16281643
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 ;
16321647 }
16331648
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);
16361654 }
1655+ }
16371656
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);
16541662 }
1663+ if (mounted) {
1664+ setState (() { _updateChildren (); });
1665+ }
1666+ return Future <void >.value ();
1667+ }
16551668
1669+ Future <void > _warpToNonAdjacentTab (Duration duration) async {
1670+ final int previousIndex = _controller! .previousIndex;
16561671 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.
16571675 final int initialPage = _currentIndex! > previousIndex
16581676 ? _currentIndex! - 1
16591677 : _currentIndex! + 1 ;
1660- final List <Widget > originalChildren = _childrenWithKey;
1661- setState (() {
1662- _warpUnderwayCount += 1 ;
16631678
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.
16641683 _childrenWithKey = List <Widget >.of (_childrenWithKey, growable: false );
16651684 final Widget temp = _childrenWithKey[initialPage];
16661685 _childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
16671686 _childrenWithKey[previousIndex] = temp;
16681687 });
1669- _pageController.jumpToPage (initialPage);
16701688
1689+ // Make a first jump to the adjacent page.
1690+ _jumpToPage (initialPage);
1691+
1692+ // Jump or animate to the destination page.
16711693 if (duration == Duration .zero) {
1672- _pageController. jumpToPage (_currentIndex! );
1694+ _jumpToPage (_currentIndex! );
16731695 } else {
1674- await _pageController.animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1696+ await _animateToPage (_currentIndex! , duration: duration, curve: Curves .ease);
1697+ }
16751698
1676- if (! mounted) {
1677- return Future <void >.value ();
1678- }
1699+ if (mounted) {
1700+ setState (() { _updateChildren (); });
16791701 }
1702+ }
16801703
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 );
16891706 }
16901707
16911708 // Called when the PageView scrolls
16921709 bool _handleScrollNotification (ScrollNotification notification) {
1693- if (_warpUnderwayCount > 0 ) {
1710+ if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0 ) {
16941711 return false ;
16951712 }
16961713
16971714 if (notification.depth != 0 ) {
16981715 return false ;
16991716 }
17001717
1701- _warpUnderwayCount += 1 ;
1718+ _scrollUnderwayCount += 1 ;
17021719 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) {
17041722 _controller! .index = _pageController.page! .round ();
17051723 _currentIndex = _controller! .index;
17061724 }
1707- _controller ! .offset = clampDouble (_pageController.page ! - _controller ! .index, - 1.0 , 1.0 );
1725+ _syncControllerOffset ( );
17081726 } else if (notification is ScrollEndNotification ) {
17091727 _controller! .index = _pageController.page! .round ();
17101728 _currentIndex = _controller! .index;
17111729 if (! _controller! .indexIsChanging) {
1712- _controller ! .offset = clampDouble (_pageController.page ! - _controller ! .index, - 1.0 , 1.0 );
1730+ _syncControllerOffset ( );
17131731 }
17141732 }
1715- _warpUnderwayCount -= 1 ;
1733+ _scrollUnderwayCount -= 1 ;
17161734
17171735 return false ;
17181736 }
0 commit comments