preface

To solve the problem of nested TabBarView scrolling, extended_tabs was written a year ago, primarily using OverscrollNotification notifications to pass scrolling to the parent TabBarView. However, this mechanism will cause the subtabbarView to not get the scroll event until the end of the scroll event, so it will feel stuck.

Later, when I made the image zooming gesture, I found that PageView would conflict with the gesture, so I removed the PageView gesture and used RawGestureDetector to monitor to control the image zooming and PageView gesture. For details, you can see the previous introduction. Juejin. Cn/post / 684490… .

To solve the problem of TabBarView nested scrolling, we can also take control of the gesture. Web page example

Code time

Get the parent and child

  void _updateAncestor() {
    if(_ancestor ! =null) {
      _ancestor._child = null;
      _ancestor = null;
    }
    if(widget.link) { _ancestor = context.findAncestorStateOfType<_ExtendedTabBarViewState>(); _ancestor? ._child =this; }}Copy the code

letPageViewLoss of rolling

First of all, we need to let the PageView lose effect, is simple, we give it a NeverScrollableScrollPhysics. The following code _defaultScrollPhysics is a NeverScrollableScrollPhysics

  void _updatePhysics() {
    _physics = _defaultScrollPhysics.applyTo(widget.physics == null
        ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
        : const PageScrollPhysics().applyTo(widget.physics));

    if (widget.physics == null) {
      _canMove = true;
    } else{ _canMove = widget.physics.shouldAcceptUserOffset(_testPageMetrics); }}Copy the code

– Added gesture monitor

ScrollableState: ScrollableState: github.com/flutter/flu…

  void _initGestureRecognizers([ExtendedTabBarView oldWidget]) {
    if (oldWidget == null|| oldWidget.scrollDirection ! = widget.scrollDirection || oldWidget.physics ! = widget.physics) {if (_canMove) {
        switch (widget.scrollDirection) {
          case Axis.vertical:
            _gestureRecognizers = <Type, GestureRecognizerFactory>{ VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers< VerticalDragGestureRecognizer>( () => VerticalDragGestureRecognizer(), (VerticalDragGestureRecognizer instance) { instance .. onDown = _handleDragDown .. onStart = _handleDragStart .. onUpdate = _handleDragUpdate .. onEnd = _handleDragEnd .. onCancel = _handleDragCancel .. minFlingDistance = widget.physics? .minFlingDistance .. minFlingVelocity = widget.physics? .minFlingVelocity .. maxFlingVelocity = widget.physics? .maxFlingVelocity; })};break;
          case Axis.horizontal:
            _gestureRecognizers = <Type, GestureRecognizerFactory>{ HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers< HorizontalDragGestureRecognizer>( () => HorizontalDragGestureRecognizer(), (HorizontalDragGestureRecognizer instance) { instance .. onDown = _handleDragDown .. onStart = _handleDragStart .. onUpdate = _handleDragUpdate .. onEnd = _handleDragEnd .. onCancel = _handleDragCancel .. minFlingDistance = widget.physics? .minFlingDistance .. minFlingVelocity = widget.physics? .minFlingVelocity .. maxFlingVelocity = widget.physics? .maxFlingVelocity; })};break; }}}}Copy the code

Wrap the returned Widget in RawGestureDetector in the build method

    if (_canMove) {
      result = RawGestureDetector(
        gestures: _gestureRecognizers,
        behavior: HitTestBehavior.opaque,
        child: result,
      );
    }
    return result;
Copy the code

Deal with gestures

  • Gesture event_hold_dragpositionClosely linked, the code is relatively simple, in the down, start, update, end, cancel event to make the corresponding processing, so that the gesture can be passed toposition.
  Drag _drag;
  ScrollHoldController _hold;

  void _handleDragDown(DragDownDetails details) {
    assert(_drag == null);
    assert(_hold == null);
    _hold = position.hold(_disposeHold);
  }

  void _handleDragStart(DragStartDetails details) {
    // It's possible for _hold to become null between _handleDragDown and
    // _handleDragStart, for example if some user code calls jumpTo or otherwise
    // triggers a new activity to begin.
    assert(_drag == null);
    _drag = position.drag(details, _disposeDrag);
    assert(_drag ! =null);
    assert(_hold == null);
  }

  void _handleDragUpdate(DragUpdateDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _drag? .update(details); }void _handleDragEnd(DragEndDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _drag? .end(details);assert(_drag == null);
  }

  void _handleDragCancel() {
    // _hold might be null if the drag started.
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _hold? .cancel(); _drag? .cancel();assert(_hold == null);
    assert(_drag == null);
  }

  void _disposeHold() {
    _hold = null;
  }

  void _disposeDrag() {
    _drag = null;
  }
Copy the code
  • In the implementation of extended_Tabs, we mainly need to consider the status of the parent and child TabbarViews when the update (hold and start can’t tell the direction of the gesture) finds it impossible to drag (minimum and maximum).

  • In the _handleAncestorChild method, we take the condition to determine whether the parent and child satisfy the ability to scroll, respectively.

2. ExtentAfter == 0 reaches maximum 3. ExtentBefore == 0 reaches minimum

  void _handleDragUpdate(DragUpdateDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _handleAncestorOrChild(details, _ancestor); _handleAncestorOrChild(details, _child); _drag? .update(details); }bool _handleAncestorOrChild(
      DragUpdateDetails details, _ExtendedTabBarViewState state) {
    if(state? ._position ! =null) {
      final double delta = widget.scrollDirection == Axis.horizontal
          ? details.delta.dx
          : details.delta.dy;
      
      if ((delta < 0 &&
              _position.extentAfter == 0&& state._position.extentAfter ! =0) ||
          (delta > 0 &&
              _position.extentBefore == 0&& state._position.extentBefore ! =0)) {
        if (state._drag == null && state._hold == null) {
          state._handleDragDown(null);
        }

        if (state._drag == null) {
          state._handleDragStart(DragStartDetails(
            globalPosition: details.globalPosition,
            localPosition: details.localPosition,
            sourceTimeStamp: details.sourceTimeStamp,
          ));
        }
        state._handleDragUpdate(details);
        return true; }}return false;
  }
Copy the code
  • Finally, in the end and Canel events, we can also do the corresponding operations for the parent and child.
  void _handleDragEnd(DragEndDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _ancestor? ._drag? .end(details); _child? ._drag? .end(details); _drag? .end(details);assert(_drag == null);
  }

  void _handleDragCancel() {
    // _hold might be null if the drag started.
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null); _ancestor? ._hold? .cancel(); _ancestor? ._drag? .cancel(); _child? ._hold? .cancel(); _child? ._drag? .cancel(); _hold? .cancel(); _drag? .cancel();assert(_hold == null);
    assert(_drag == null);
  }

Copy the code

use

dependencies:
  flutter:
    sdk: flutter
  extended_tabs: any
Copy the code

Color block indicator

    TabBar(
      indicator: ColorTabIndicator(Colors.blue),
      labelColor: Colors.black,
      tabs: [
        Tab(text: "Tab0"),
        Tab(text: "Tab1"),
      ],
      controller: tabController,
    )
Copy the code

Nested rolling

  /// If enabled, when the current TabBarView cannot scroll, it will check whether the parent and child TabbarViews can scroll.
  /// If you can scroll, you'll scroll directly to the parent and child
  /// The default open
  final bool link;
  
  ExtendedTabBarView(
    children: <Widget>[
      List("Tab000"),
      List("Tab001"),
      List("Tab002"),
      List("Tab003"),
    ],
    controller: tabController2,
    link: true.)Copy the code

Rolling direction

  /// Rolling direction
  /// The default is horizontal scrolling
  final Axis scrollDirection;

  Row(
    children: <Widget>[
      ExtendedTabBar(
        indicator: const ColorTabIndicator(Colors.blue),
        labelColor: Colors.black,
        scrollDirection: Axis.vertical,
        tabs: const <ExtendedTab>[
          ExtendedTab(
            text: 'Tab0',
            scrollDirection: Axis.vertical,
          ),
          ExtendedTab(
            text: 'Tab1',
            scrollDirection: Axis.vertical,
          ),
        ],
        controller: tabController,
      ),
      Expanded(
        child: ExtendedTabBarView(
          children: <Widget>[
            const ListWidget(
              'Tab1',
              scrollDirection: Axis.horizontal,
            ),
            const ListWidget(
              'Tab1',
              scrollDirection: Axis.horizontal,
            ),
          ],
          controller: tabController,
          scrollDirection: Axis.vertical,
        ),
      )
    ],
  )
Copy the code

The cache size

  /// Number of cached pages
  /// The default is 0
  /// If set to 1, it means there are two pages in memory
  final int cacheExtent;
  
  ExtendedTabBarView(
    children: <Widget>[
      List("Tab000"),
      List("Tab001"),
      List("Tab002"),
      List("Tab003"),
    ],
    controller: tabController2,
    cacheExtent: 1.)Copy the code

conclusion

There are only two weeks left in 2020. It is an extraordinary year. Fortunately, everyone around us is safe and sound. Having seen so much firsthand, sometimes it feels good just to be healthy and write code. Most of the time, as long as you put your heart into it, no matter how difficult the problem is, whether it is in work, study or life, I believe we will overcome it.

Love Flutter, love candy, welcome aboardFlutter CandiesTogether they produce cute little candies called FlutterQQ group: 181398081

Finally put the Flutter on the bucket. It smells good.