Files
PiliPlus/lib/scripts/scrollable_gesture.patch
2026-07-31 20:21:09 +08:00

319 lines
12 KiB
Diff

diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index 87a611a6f60..843a05d15e6 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -9,7 +9,7 @@ import 'dart:math' as math;
import 'dart:ui' show SemanticsRole, lerpDouble;
import 'package:flutter/foundation.dart';
-import 'package:flutter/gestures.dart' show DragStartBehavior;
+import 'package:flutter/gestures.dart' show DragStartBehavior, HorizontalDragGestureRecognizer;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
@@ -2222,8 +2222,17 @@ class TabBarView extends StatefulWidget {
this.dragStartBehavior = DragStartBehavior.start,
this.viewportFraction = 1.0,
this.clipBehavior = Clip.hardEdge,
+ this.scrollDirection = Axis.horizontal,
+ this.hitTestBehavior = HitTestBehavior.opaque,
+ this.horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer.new,
});
+ final Axis scrollDirection;
+
+ final HitTestBehavior hitTestBehavior;
+
+ final ValueGetter<HorizontalDragGestureRecognizer> horizontalDragGestureRecognizer;
+
/// This widget's selection and animation state.
///
/// If [TabController] is not provided, then the value of [DefaultTabController.of]
@@ -2525,6 +2534,9 @@ class _TabBarViewState extends State<TabBarView> {
dragStartBehavior: widget.dragStartBehavior,
clipBehavior: widget.clipBehavior,
controller: _pageController,
+ scrollDirection: widget.scrollDirection,
+ hitTestBehavior: widget.hitTestBehavior,
+ horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
physics: widget.physics == null
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
: const PageScrollPhysics().applyTo(widget.physics),
diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart
index 158e7c321ed..b0d1205895f 100644
--- a/packages/flutter/lib/src/widgets/gesture_detector.dart
+++ b/packages/flutter/lib/src/widgets/gesture_detector.dart
@@ -1430,7 +1430,7 @@ class RawGestureDetector extends StatefulWidget {
}
/// State for a [RawGestureDetector].
-class RawGestureDetectorState extends State<RawGestureDetector> {
+class RawGestureDetectorState<T extends RawGestureDetector> extends State<T> {
Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
SemanticsGestureDelegate? _semantics;
@@ -1444,7 +1444,7 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
@protected
@override
- void didUpdateWidget(RawGestureDetector oldWidget) {
+ void didUpdateWidget(T oldWidget) {
super.didUpdateWidget(oldWidget);
if (!(oldWidget.semantics == null && widget.semantics == null)) {
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
@@ -1636,6 +1636,109 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
}
}
+class NestedRawGestureDetector extends RawGestureDetector {
+ const NestedRawGestureDetector({
+ super.key,
+ super.child,
+ super.gestures,
+ super.behavior,
+ super.excludeFromSemantics,
+ super.semantics,
+ required this.ignoring,
+ });
+
+ final ValueGetter<bool> ignoring;
+
+ @override
+ RawGestureDetectorState createState() => NestedRawGestureDetectorState();
+}
+
+class NestedRawGestureDetectorState extends RawGestureDetectorState<NestedRawGestureDetector> {
+ @override
+ Widget build(BuildContext context) {
+ Widget result = NestedListener(
+ onPointerDown: _handlePointerDown,
+ onPointerPanZoomStart: _handlePointerPanZoomStart,
+ behavior: widget.behavior ?? _defaultBehavior,
+ ignoring: widget.ignoring,
+ child: widget.child,
+ );
+ if (!widget.excludeFromSemantics) {
+ result = _GestureSemantics(
+ behavior: widget.behavior ?? _defaultBehavior,
+ assignSemantics: _updateSemanticsForRenderObject,
+ child: result,
+ );
+ }
+ return result;
+ }
+}
+
+class NestedListener extends Listener {
+ const NestedListener({
+ super.key,
+ super.onPointerDown,
+ super.onPointerMove,
+ super.onPointerUp,
+ super.onPointerHover,
+ super.onPointerCancel,
+ super.onPointerPanZoomStart,
+ super.onPointerPanZoomUpdate,
+ super.onPointerPanZoomEnd,
+ super.onPointerSignal,
+ super.behavior,
+ required this.ignoring,
+ super.child,
+ });
+
+ final ValueGetter<bool> ignoring;
+
+ @override
+ RenderPointerListener createRenderObject(BuildContext context) {
+ return _RenderPointerListener(
+ onPointerDown: onPointerDown,
+ onPointerMove: onPointerMove,
+ onPointerUp: onPointerUp,
+ onPointerHover: onPointerHover,
+ onPointerCancel: onPointerCancel,
+ onPointerPanZoomStart: onPointerPanZoomStart,
+ onPointerPanZoomUpdate: onPointerPanZoomUpdate,
+ onPointerPanZoomEnd: onPointerPanZoomEnd,
+ onPointerSignal: onPointerSignal,
+ behavior: behavior,
+ ignoring: ignoring,
+ );
+ }
+
+ @override
+ void updateRenderObject(BuildContext context, _RenderPointerListener renderObject) {
+ super.updateRenderObject(context, renderObject);
+ renderObject.ignoring = ignoring;
+ }
+}
+
+class _RenderPointerListener extends RenderPointerListener {
+ _RenderPointerListener({
+ super.onPointerDown,
+ super.onPointerMove,
+ super.onPointerUp,
+ super.onPointerHover,
+ super.onPointerCancel,
+ super.onPointerPanZoomStart,
+ super.onPointerPanZoomUpdate,
+ super.onPointerPanZoomEnd,
+ super.onPointerSignal,
+ super.behavior,
+ required this.ignoring,
+ super.child,
+ });
+
+ ValueGetter<bool> ignoring;
+
+ @override
+ bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque && ignoring();
+}
+
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);
class _GestureSemantics extends SingleChildRenderObjectWidget {
diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart
index 59fa2544abe..43c57ad5f56 100644
--- a/packages/flutter/lib/src/widgets/page_view.dart
+++ b/packages/flutter/lib/src/widgets/page_view.dart
@@ -11,7 +11,7 @@ library;
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show clampDouble, precisionErrorTolerance;
-import 'package:flutter/gestures.dart' show DragStartBehavior;
+import 'package:flutter/gestures.dart' show DragStartBehavior, HorizontalDragGestureRecognizer;
import 'package:flutter/rendering.dart';
import 'basic.dart';
@@ -697,6 +697,7 @@ class PageView extends StatefulWidget {
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
+ this.horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer.new,
}) : assert(
scrollCacheExtent == null || (scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
@@ -751,6 +752,7 @@ class PageView extends StatefulWidget {
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
+ this.horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer.new,
}) : assert(
scrollCacheExtent == null || (scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
@@ -793,6 +795,7 @@ class PageView extends StatefulWidget {
this.hitTestBehavior = HitTestBehavior.opaque,
this.scrollBehavior,
this.padEnds = true,
+ this.horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer.new,
}) : assert(
scrollCacheExtent == null || (scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
@@ -802,6 +805,8 @@ class PageView extends StatefulWidget {
scrollCacheExtent =
scrollCacheExtent ?? ScrollCacheExtent.viewport(allowImplicitScrolling ? 1.0 : 0.0);
+ final ValueGetter<HorizontalDragGestureRecognizer> horizontalDragGestureRecognizer;
+
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
/// Controls whether the widget's pages will respond to
/// [RenderObject.showOnScreen], which will allow for implicit accessibility
@@ -1021,6 +1026,7 @@ class _PageViewState extends State<PageView> {
physics: physics,
restorationId: widget.restorationId,
hitTestBehavior: widget.hitTestBehavior,
+ horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
scrollBehavior:
widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(scrollbars: false),
viewportBuilder: (BuildContext context, ViewportOffset position) {
diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart
index 9df2d633f16..001565fbb00 100644
--- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -45,6 +45,8 @@ import 'viewport.dart';
export 'package:flutter/physics.dart' show Tolerance;
+const _nestedOuterDebugLabel = 'outer';
+
// Examples can assume:
// late BuildContext context;
@@ -134,8 +136,11 @@ class Scrollable extends StatefulWidget {
this.scrollBehavior,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
+ this.horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer.new,
}) : assert(semanticChildCount == null || semanticChildCount >= 0);
+ final ValueGetter<HorizontalDragGestureRecognizer> horizontalDragGestureRecognizer;
+
/// {@template flutter.widgets.Scrollable.axisDirection}
/// The direction in which this widget scrolls.
///
@@ -811,8 +816,7 @@ class ScrollableState extends State<Scrollable>
_gestureRecognizers = <Type, GestureRecognizerFactory>{
HorizontalDragGestureRecognizer:
GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
- () =>
- HorizontalDragGestureRecognizer(supportedDevices: _configuration.dragDevices),
+ widget.horizontalDragGestureRecognizer,
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
@@ -1020,26 +1024,37 @@ class ScrollableState extends State<Scrollable>
//
// Since notificationContext is pointing to _gestureDetectorKey.context, _ScrollableScope
// must be placed above the widget using it: RawGestureDetector
- Widget result = _ScrollableScope(
+ Widget result = Semantics(
+ explicitChildNodes: !widget.excludeFromSemantics,
+ child: IgnorePointer(
+ key: _ignorePointerKey,
+ ignoring: _shouldIgnorePointer,
+ child: widget.viewportBuilder(context, position),
+ ),
+ );
+ if (_effectiveScrollController.debugLabel == _nestedOuterDebugLabel) {
+ result = NestedRawGestureDetector(
+ key: _gestureDetectorKey,
+ gestures: _gestureRecognizers,
+ behavior: widget.hitTestBehavior,
+ excludeFromSemantics: widget.excludeFromSemantics,
+ ignoring: () => _shouldIgnorePointer,
+ child: result,
+ );
+ } else {
+ result = RawGestureDetector(
+ key: _gestureDetectorKey,
+ gestures: _gestureRecognizers,
+ behavior: widget.hitTestBehavior,
+ excludeFromSemantics: widget.excludeFromSemantics,
+ child: result,
+ );
+ }
+
+ result = _ScrollableScope(
scrollable: this,
position: position,
- child: Listener(
- onPointerSignal: _receivedPointerSignal,
- child: RawGestureDetector(
- key: _gestureDetectorKey,
- gestures: _gestureRecognizers,
- behavior: widget.hitTestBehavior,
- excludeFromSemantics: widget.excludeFromSemantics,
- child: Semantics(
- explicitChildNodes: !widget.excludeFromSemantics,
- child: IgnorePointer(
- key: _ignorePointerKey,
- ignoring: _shouldIgnorePointer,
- child: widget.viewportBuilder(context, position),
- ),
- ),
- ),
- ),
+ child: Listener(onPointerSignal: _receivedPointerSignal, child: result),
);
if (!widget.excludeFromSemantics) {