Advertisement
Armen_06

Untitled

Apr 20th, 2024
490
0
22 hours
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Dart 10.90 KB | None | 0 0
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. part of 'package:nested_scroll_views/src/assembly/material/tabs.dart';
  6.  
  7. /// A page view that displays the widget which corresponds to the currently
  8. /// selected tab.
  9. ///
  10. /// This widget is typically used in conjunction with a [TabBar].
  11. ///
  12. /// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
  13. ///
  14. /// If a [TabController] is not provided, then there must be a [DefaultTabController]
  15. /// ancestor.
  16. ///
  17. /// The tab controller's [TabController.length] must equal the length of the
  18. /// [children] list and the length of the [TabBar.tabs] list.
  19. ///
  20. /// To see a sample implementation, visit the [TabController] documentation.
  21. class TabBarView extends StatefulWidget {
  22.   /// Creates a page view with one child per tab.
  23.   ///
  24.   /// The length of [children] must be the same as the [controller]'s length.
  25.   const TabBarView({
  26.     super.key,
  27.     required this.children,
  28.     this.controller,
  29.     this.physics,
  30.     this.dragStartBehavior = DragStartBehavior.start,
  31.     this.viewportFraction = 1.0,
  32.     this.clipBehavior = Clip.hardEdge,
  33.   });
  34.  
  35.   /// This widget's selection and animation state.
  36.   ///
  37.   /// If [TabController] is not provided, then the value of [DefaultTabController.of]
  38.   /// will be used.
  39.   final TabController? controller;
  40.  
  41.   /// One widget per tab.
  42.   ///
  43.   /// Its length must match the length of the [TabBar.tabs]
  44.   /// list, as well as the [controller]'s [TabController.length].
  45.   final List<Widget> children;
  46.  
  47.   /// How the page view should respond to user input.
  48.   ///
  49.   /// For example, determines how the page view continues to animate after the
  50.   /// user stops dragging the page view.
  51.   ///
  52.   /// The physics are modified to snap to page boundaries using
  53.   /// [PageScrollPhysics] prior to being used.
  54.   ///
  55.   /// Defaults to matching platform conventions.
  56.   final ScrollPhysics? physics;
  57.  
  58.   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  59.   final DragStartBehavior dragStartBehavior;
  60.  
  61.   /// {@macro flutter.widgets.pageview.viewportFraction}
  62.   final double viewportFraction;
  63.  
  64.   /// {@macro flutter.material.Material.clipBehavior}
  65.   ///
  66.   /// Defaults to [Clip.hardEdge].
  67.   final Clip clipBehavior;
  68.  
  69.   @override
  70.   State<TabBarView> createState() => _TabBarViewState();
  71. }
  72.  
  73. class _TabBarViewState extends State<TabBarView> {
  74.   TabController? _controller;
  75.   PageController? _pageController;
  76.   late List<Widget> _childrenWithKey;
  77.   int? _currentIndex;
  78.   int _warpUnderwayCount = 0;
  79.   int _scrollUnderwayCount = 0;
  80.   bool _debugHasScheduledValidChildrenCountCheck = false;
  81.  
  82.   // If the TabBarView is rebuilt with a new tab controller, the caller should
  83.   // dispose the old one. In that case the old controller's animation will be
  84.   // null and should not be accessed.
  85.   bool get _controllerIsValid => _controller?.animation != null;
  86.  
  87.   void _updateTabController() {
  88.     final TabController? newController = widget.controller ?? DefaultTabController.maybeOf(context);
  89.     assert(() {
  90.       if (newController == null) {
  91.         throw FlutterError(
  92.           'No TabController for ${widget.runtimeType}.\n'
  93.           'When creating a ${widget.runtimeType}, you must either provide an explicit '
  94.           'TabController using the "controller" property, or you must ensure that there '
  95.           'is a DefaultTabController above the ${widget.runtimeType}.\n'
  96.           'In this case, there was neither an explicit controller nor a default controller.',
  97.         );
  98.       }
  99.       return true;
  100.     }());
  101.  
  102.     if (newController == _controller) {
  103.       return;
  104.     }
  105.  
  106.     if (_controllerIsValid) {
  107.       _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
  108.     }
  109.     _controller = newController;
  110.     if (_controller != null) {
  111.       _controller!.animation!.addListener(_handleTabControllerAnimationTick);
  112.     }
  113.   }
  114.  
  115.   void _jumpToPage(int page) {
  116.     _warpUnderwayCount += 1;
  117.     _pageController!.jumpToPage(page);
  118.     _warpUnderwayCount -= 1;
  119.   }
  120.  
  121.   Future<void> _animateToPage(
  122.     int page, {
  123.     required Duration duration,
  124.     required Curve curve,
  125.   }) async {
  126.     _warpUnderwayCount += 1;
  127.     await _pageController!.animateToPage(page, duration: duration, curve: curve);
  128.     _warpUnderwayCount -= 1;
  129.   }
  130.  
  131.   @override
  132.   void initState() {
  133.     super.initState();
  134.     _updateChildren();
  135.   }
  136.  
  137.   @override
  138.   void didChangeDependencies() {
  139.     super.didChangeDependencies();
  140.     _updateTabController();
  141.     _currentIndex = _controller!.index;
  142.     // TODO(chunhtai): https://github.com/flutter/flutter/issues/134253
  143.     _pageController?.dispose();
  144.     _pageController = PageController(
  145.       initialPage: _currentIndex!,
  146.       viewportFraction: widget.viewportFraction,
  147.     );
  148.   }
  149.  
  150.   @override
  151.   void didUpdateWidget(TabBarView oldWidget) {
  152.     super.didUpdateWidget(oldWidget);
  153.     if (widget.controller != oldWidget.controller) {
  154.       _updateTabController();
  155.       _currentIndex = _controller!.index;
  156.       _jumpToPage(_currentIndex!);
  157.     }
  158.     if (widget.viewportFraction != oldWidget.viewportFraction) {
  159.       _pageController?.dispose();
  160.       _pageController = PageController(
  161.         initialPage: _currentIndex!,
  162.         viewportFraction: widget.viewportFraction,
  163.       );
  164.     }
  165.     // While a warp is under way, we stop updating the tab page contents.
  166.     // This is tracked in https://github.com/flutter/flutter/issues/31269.
  167.     if (widget.children != oldWidget.children && _warpUnderwayCount == 0) {
  168.       _updateChildren();
  169.     }
  170.   }
  171.  
  172.   @override
  173.   void dispose() {
  174.     if (_controllerIsValid) {
  175.       _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
  176.     }
  177.     _controller = null;
  178.     _pageController?.dispose();
  179.     // We don't own the _controller Animation, so it's not disposed here.
  180.     super.dispose();
  181.   }
  182.  
  183.   void _updateChildren() {
  184.     _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
  185.   }
  186.  
  187.   void _handleTabControllerAnimationTick() {
  188.     if (_scrollUnderwayCount > 0 || !_controller!.indexIsChanging) {
  189.       return;
  190.     } // This widget is driving the controller's animation.
  191.  
  192.     if (_controller!.index != _currentIndex) {
  193.       _currentIndex = _controller!.index;
  194.       _warpToCurrentIndex();
  195.     }
  196.   }
  197.  
  198.   void _warpToCurrentIndex() {
  199.     if (!mounted || _pageController!.page == _currentIndex!.toDouble()) {
  200.       return;
  201.     }
  202.  
  203.     final bool adjacentDestination = (_currentIndex! - _controller!.previousIndex).abs() == 1;
  204.     if (adjacentDestination) {
  205.       _warpToAdjacentTab(_controller!.animationDuration);
  206.     } else {
  207.       _warpToNonAdjacentTab(_controller!.animationDuration);
  208.     }
  209.   }
  210.  
  211.   Future<void> _warpToAdjacentTab(Duration duration) async {
  212.     if (duration == Duration.zero) {
  213.       _jumpToPage(_currentIndex!);
  214.     } else {
  215.       await _animateToPage(_currentIndex!, duration: duration, curve: Curves.ease);
  216.     }
  217.     if (mounted) {
  218.       setState(() { _updateChildren(); });
  219.     }
  220.     return Future<void>.value();
  221.   }
  222.  
  223.   Future<void> _warpToNonAdjacentTab(Duration duration) async {
  224.     final int previousIndex = _controller!.previousIndex;
  225.     assert((_currentIndex! - previousIndex).abs() > 1);
  226.  
  227.     // initialPage defines which page is shown when starting the animation.
  228.     // This page is adjacent to the destination page.
  229.     final int initialPage = _currentIndex! > previousIndex
  230.         ? _currentIndex! - 1
  231.         : _currentIndex! + 1;
  232.  
  233.     setState(() {
  234.       // Needed for `RenderSliverMultiBoxAdaptor.move` and kept alive children.
  235.       // For motivation, see https://github.com/flutter/flutter/pull/29188 and
  236.       // https://github.com/flutter/flutter/issues/27010#issuecomment-486475152.
  237.       _childrenWithKey = List<Widget>.of(_childrenWithKey, growable: false);
  238.       final Widget temp = _childrenWithKey[initialPage];
  239.       _childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
  240.       _childrenWithKey[previousIndex] = temp;
  241.     });
  242.  
  243.     // Make a first jump to the adjacent page.
  244.     _jumpToPage(initialPage);
  245.  
  246.     // Jump or animate to the destination page.
  247.     if (duration == Duration.zero) {
  248.       _jumpToPage(_currentIndex!);
  249.     } else {
  250.       await _animateToPage(_currentIndex!, duration: duration, curve: Curves.ease);
  251.     }
  252.  
  253.     if (mounted) {
  254.       setState(() { _updateChildren(); });
  255.     }
  256.   }
  257.  
  258.   void _syncControllerOffset() {
  259.     _controller!.offset = clampDouble(_pageController!.page! - _controller!.index, -1.0, 1.0);
  260.   }
  261.  
  262.   // Called when the PageView scrolls
  263.   bool _handleScrollNotification(ScrollNotification notification) {
  264.     if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0) {
  265.       return false;
  266.     }
  267.  
  268.     if (notification.depth != 0) {
  269.       return false;
  270.     }
  271.  
  272.     if (!_controllerIsValid) {
  273.       return false;
  274.     }
  275.  
  276.     _scrollUnderwayCount += 1;
  277.     final double page = _pageController!.page!;
  278.     if (notification is ScrollUpdateNotification && !_controller!.indexIsChanging) {
  279.       final bool pageChanged = (page - _controller!.index).abs() > 1.0;
  280.       if (pageChanged) {
  281.         _controller!.index = page.round();
  282.         _currentIndex =_controller!.index;
  283.       }
  284.       _syncControllerOffset();
  285.     } else if (notification is ScrollEndNotification) {
  286.       _controller!.index = page.round();
  287.       _currentIndex = _controller!.index;
  288.       if (!_controller!.indexIsChanging) {
  289.         _syncControllerOffset();
  290.       }
  291.     }
  292.     _scrollUnderwayCount -= 1;
  293.  
  294.     return false;
  295.   }
  296.  
  297.   bool _debugScheduleCheckHasValidChildrenCount() {
  298.     if (_debugHasScheduledValidChildrenCountCheck) {
  299.       return true;
  300.     }
  301.     WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
  302.       _debugHasScheduledValidChildrenCountCheck = false;
  303.       if (!mounted) {
  304.         return;
  305.       }
  306.       assert(() {
  307.         if (_controller!.length != widget.children.length) {
  308.           throw FlutterError(
  309.             "Controller's length property (${_controller!.length}) does not match the "
  310.             "number of children (${widget.children.length}) present in TabBarView's children property.",
  311.           );
  312.         }
  313.         return true;
  314.       }());
  315.     });
  316.     _debugHasScheduledValidChildrenCountCheck = true;
  317.     return true;
  318.   }
  319.  
  320.   @override
  321.   Widget build(BuildContext context) {
  322.     assert(_debugScheduleCheckHasValidChildrenCount());
  323.  
  324.     return NotificationListener<ScrollNotification>(
  325.       onNotification: _handleScrollNotification,
  326.       child: PageView(
  327.         dragStartBehavior: widget.dragStartBehavior,
  328.         clipBehavior: widget.clipBehavior,
  329.         controller: _pageController,
  330.         physics: widget.physics == null
  331.           ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
  332.           : const PageScrollPhysics().applyTo(widget.physics),
  333.         children: _childrenWithKey,
  334.       ),
  335.     );
  336.   }
  337. }
  338.  
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement