Flutter TabBarView 嵌套TabBarView, 滑动联动

需求

内容式的App 会有很多内容显示,需要很多卡片,TabBarView 嵌套是很正常的,很多时候产品会要求嵌套的TabBarView 滚动到边缘时候,要和外面的TabBarView流畅地联动。

TabBarView 基本流程

我们先来看TabBarView的基本流程:


Flutter TabBarView 嵌套TabBarView, 滑动联动_第1张图片
image.png

TabBarView 实际上是一个PageView
TabBarView 捕捉PageView 滑动:更新tabController.offset, 通知indicator 位置变化; 更新tabController.index 修改TabBarView 显示的 Tab。
PageView 是Scrollable + Viewport
PageView 通过Scrollable 滑动,进行更改通知onPageChanged;

那么PageView 是怎么实现页面滑动的呢?
Scrollable 内部 创建一个ScrollPosition, ScrollPosition 绑定到ScrollController, 当进行手势的时候,比如滑动,拖拽等, 将手势产生的变化通过修改ScrollPosition同步给ScrollController, 同时发送ScrollNotification。

所以,关键在于,手势修改ScrollPosition, 实现视图滚动。 详细可以阅读flutter/src/gestures/drag.dart.

那么实现嵌套的TabBarView 联动,需要:

  • 将内部TabBarView的边缘滚动事件屏蔽;
  • TabBarView外部接管内部TabBarView的边缘滚动事件;
    基本流程图如下


    Flutter TabBarView 嵌套TabBarView, 滑动联动_第2张图片
    image.png

实现要点

  • 滑动屏蔽内部滚动事件
    • Scrollable.class:
      void _handleDragUpdate(DragUpdateDetails details) {
           // _drag might be null if the drag activity ended and called _disposeDrag.
           assert(_hold == null || _drag == null);
           if (_overscroll) {
               return;
           }
           _drag?.update(details);
       }
      
      OverscrollNotification 为通知外部TabBarView 滑动。
      当边缘滚动时候,会触发_onPointerGiveUp, 并发送OverscrollNotification。
        void _onPointerGiveUp(DragUpdateDetails dragDetails) {
              double offset = position.pixels -
                   position.physics
                          .applyPhysicsToUserOffset(position, dragDetails.primaryDelta);
              if (offset == 0.0) {
                  return;
              }
      
            final double overscroll =
                 position.physics.applyBoundaryConditions(position, offset);
      
            if (overscroll == 0.0) {
                OverscrollNotification(
                        metrics: position.copyWith(),
                        context: context,
                        dragDetails: dragDetails,
                        overscroll: -offset)
                    .dispatch(context);
                return;
             }
      
              OverscrollNotification(
                      metrics: position.copyWith(),
                      context: context,
                      dragDetails: dragDetails,
                      overscroll: overscroll)
                  .dispatch(context);
          }
      
  • 内部的边缘滚动事件转化成外部的滚动事件
    原理:position 修改需要使用Drag,进行update。
    接收到OverscrollNotification 时候,UnionOuterGestureDelegate.class:
    /// 将处理UnionScrollNotification.
    bool handleUnionScrollNotification(
        BuildContext context, UnionScrollNotification notification) {
      if (tabController.index != notification.index) {
        return false;
      }
    
      if (notification is UnionScrollStartNotification) {
        _drag = pageController.position.drag(notification.dragDetails, () {
          _drag = null;
        });
      } else if (notification is UnionOverscrollNotification) {
        if (_drag == null) {
          return true;
        }
    
        /// 计算用户滑动
        /// update the offset, to update the indicator's position
        MediaQueryData data = MediaQuery.of(context);
        tabController.offset =
            (tabController.offset + notification.overscroll / data.size.width)
                .clamp(-1.0, 1.0);
    
        if (notification.dragDetails != null) {
          /// update the viewpager's position
          _drag.update(notification.dragDetails);
        }
      } else if (notification is UnionScrollEndNotification) {
        _drag?.cancel();
        _drag = null;
      } else if (notification is UnionScrollUpdateNotification) {
        if (_drag != null && notification.dragDetails != null) {
          /// update the viewpager's position
          _drag.update(notification.dragDetails);
    
          /// 计算用户滑动
          /// update the offset, to update the indicator's position
          MediaQueryData data = MediaQuery.of(context);
          tabController.offset = (tabController.offset +
                  notification.dragDetails.delta.dx / data.size.width)
              .clamp(-1.0, 1.0);
        }
      }
      return true;
    }
    

效果图

Flutter TabBarView 嵌套TabBarView, 滑动联动_第3张图片
image.png

使用

Github: https://github.com/wilin52/union_tabs

1.Install

dependencies:
  union_tabs: ^1.0.0+4

2.Import

import 'package:union_tabs/union_tabs.dart';

3.Usage

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          bottom: TabBar(
              controller: _controller,
              tabs: tabsText.map((it) => Tab(text: it)).toList()),
        ),
        body: UnionOuterTabBarView( /// outerTabBarView
          controller: _controller,
          children: _createTabContent(),
        ));
  }

  List _createTabContent() {
    List tabContent = List();
    tabContent.add(Center(child: Text(tabsText[0])));
    final child = Column(
      children: [
        TabBar(
            labelColor: Colors.black,
            unselectedLabelColor: Colors.black45,
            controller: _childController,
            tabs: secondTabsText.map((it) => Tab(text: it)).toList()),
        Expanded(
          child: UnionInnerTabBarView( /// innerTabBarView
              controller: _childController,
              children:
                  secondTabsText.map((it) => Center(child: Text(it))).toList()),
        )
      ],
    );
    tabContent.add(child);
    tabContent.add(Center(child: Text(tabsText[2])));
    return tabContent;
  }

你可能感兴趣的:(Flutter TabBarView 嵌套TabBarView, 滑动联动)