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
letPageView
Loss 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
和_drag
将position
Closely 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.