需求
内容式的App 会有很多内容显示,需要很多卡片,TabBarView 嵌套是很正常的,很多时候产品会要求嵌套的TabBarView 滚动到边缘时候,要和外面的TabBarView流畅地联动。
TabBarView 基本流程
我们先来看TabBarView的基本流程:
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的边缘滚动事件;
基本流程图如下
image.png
实现要点
-
滑动屏蔽内部滚动事件
- Scrollable.class:
OverscrollNotification 为通知外部TabBarView 滑动。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); }
当边缘滚动时候,会触发_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); }
- Scrollable.class:
-
内部的边缘滚动事件转化成外部的滚动事件
原理: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; }
效果图
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<Widget> _createTabContent() {
List<Widget> tabContent = List();
tabContent.add(Center(child: Text(tabsText[0])));
final child = Column(
children: <Widget>[
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;
}
网友评论