I have a vertically scrolling WebView
inside a horizontally scrolling PageView
. Something like this:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => VerticalDragGestureRecognizer()),
].toSet(),
);
},
);
With the previous stable version of Flutter (1.5.4), this worked as expected - scrolling vertically would move the content inside the WebView and scrolling horizontally would move the PageView.
This broke after upgrading to Flutter v1.7.8+hotfix.3
. Now horizontal scrolling seems always to win, even if the gesture is very clearly almost entirely vertical. If the page gets scrolled vertically at all, it is only after the gesture stops (i.e., when I stop touching the screen after a gesture) - there is no vertical scrolling while the gesture is happening.
Adding and removing VerticalDragGestureRecognizer
from gestureRecognizers
have no effect now - either way the program works as if the recognizer was not on the list (although it's not that gestureRecognizers
are completely ignored because adding EagerGestureRecognizer
DOES have an effect).
Here is the debug output of the gesture arena (keep in mind that I was trying to keep my gesture as vertical as possible, but even a slight finger movement to the sides was enough for the HorizontalDragGestureRecognizer
to win, even though I also was moving vertically the entire time):
I/flutter (30125): Gesture arena 14 ❙ ★ Opening new gesture arena.
I/flutter (30125): Gesture arena 14 ❙ Adding: Instance of '_CombiningGestureArenaMember'
I/flutter (30125): Gesture arena 14 ❙ Adding: LongPressGestureRecognizer#9cad1(debugOwner: GestureDetector, state: ready)
I/flutter (30125): Gesture arena 14 ❙ Adding: HorizontalDragGestureRecognizer#69b8f(start behavior: start)
I/flutter (30125): Gesture arena 14 ❙ Closing with 3 members.
I/flutter (30125): Gesture arena 14 ❙ Rejecting: LongPressGestureRecognizer#9cad1(debugOwner: GestureDetector, state: possible)
I/flutter (30125): Gesture arena 14 ❙ Accepting: HorizontalDragGestureRecognizer#69b8f(start behavior: start)
I/flutter (30125): Gesture arena 14 ❙ Self-declared winner: HorizontalDragGestureRecognizer#69b8f(start behavior: start)
And this is what happens when you do manage to keep your gesture entirely vertical (seems to be easier on an emulator with a mouse), while the drag gesture is in process:
flutter: Gesture arena 30 ❙ ★ Opening new gesture arena.
flutter: Gesture arena 30 ❙ Adding: Instance of '_CombiningGestureArenaMember'
flutter: Gesture arena 30 ❙ Adding: HorizontalDragGestureRecognizer#11e7f(start behavior: down)
flutter: Gesture arena 30 ❙ Closing with 2 members.
Even a slight vertical move will make the HorizontalDragGestureRecognizer
announce a win, but the VerticalDragGestureRecognizer
(which seems to be wrapped inside the _CombiningGestureArenaMember
) never claims a victory. It seems to be completely ignored in fact - the gesture arena output with VerticalDragGestureRecognizer
in gestureRecognizers
and without it is absolutely identical.
It may be a bug in Flutter so I also created an issue on Flutter's GitHub. But either way - how do I achieve this effect with the current version of Flutter? Any workarounds or canonical solutions would be highly appreciated.
Positives of horizontal scrollHorizontal scrolling saves a lot of vertical screen space. Rather than displaying all the content at once on a very long page, horizontal layouts introduce users to smaller chunks of information. The layout is much more flexible.
Users may ignore content accessible through horizontal scrolling or “swiping” as they don't expect content there. Our research found that even strong cues such as arrows frequently remain unnoticed. People expect to scroll vertically for additional content, but they don't expect to scroll sideways.
Horizontal scroll is a navigation technique for exploring web content. Instead of moving through a website page vertically from top to bottom, horizontal scrolling websites take readers through a left-to-right experience—magically, text and visuals move sideways.
Horizontal scrolling can be achieved by clicking and dragging a horizontal scroll bar, swiping sideways on a desktop trackpad or trackpad mouse, pressing left and right arrow keys, or swiping sideways with one's finger on a touchscreen.
It seems that the rules of the arena have changed. Now the arena declares wins for gestures that have active receivers. That indeed increases the responsiveness of the gestures even more. However, as the native views do not claim the gestures and only consume them when no other active detector/receiver claims them, I suspect that the vertical drag doesn't even enter the arena as a gesture from the WebView. That is why any slight horizontal drag causes horizontal drag gesture to win - because simply no other widgets claim any gesture.
You can extend VerticalDragGestureRecognizer
, so it accepts gestures:
class PlatformViewVerticalGestureRecognizer
extends VerticalDragGestureRecognizer {
PlatformViewVerticalGestureRecognizer({PointerDeviceKind kind})
: super(kind: kind);
Offset _dragDistance = Offset.zero;
@override
void addPointer(PointerEvent event) {
startTrackingPointer(event.pointer);
}
@override
void handleEvent(PointerEvent event) {
_dragDistance = _dragDistance + event.delta;
if (event is PointerMoveEvent) {
final double dy = _dragDistance.dy.abs();
final double dx = _dragDistance.dx.abs();
if (dy > dx && dy > kTouchSlop) {
// vertical drag - accept
resolve(GestureDisposition.accepted);
_dragDistance = Offset.zero;
} else if (dx > kTouchSlop && dx > dy) {
// horizontal drag - stop tracking
stopTrackingPointer(event.pointer);
_dragDistance = Offset.zero;
}
}
}
@override
String get debugDescription => 'horizontal drag (platform view)';
@override
void didStopTrackingLastPointer(int pointer) {}
}
After that, you can use the new class in gestureRecognizers
:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
);
},
);
I upgraded my sdk only to have this problem you described. The issue is too annoying and I came up with this rather ugly hack.
This CustomGestureRecognizer
will ignore the unwanted behavior when the event is occurring in the middle (usually where we scroll).
This does come with some over-scrolling shadows which I believe can be handled, might take some more time that's it.
class CustomGestureRecognizer extends OneSequenceGestureRecognizer {
double maxScreenOffsetX;
final double edgeMargin = 20.0;
CustomGestureRecognizer({@required this.maxScreenOffsetX});
@override
void addAllowedPointer(PointerDownEvent event) {
print("CustomGestureRecognizer: Screen Width: "+ maxScreenOffsetX.toString());
print("CustomGestureRecognizer: dx: "+event.position.dx.toString());
if (event.position.dx < edgeMargin || event.position.dx > (maxScreenOffsetX - edgeMargin)) {
print("CustomGestureRecognizer: At the Edge.");
return;
}
print("CustomGestureRecognizer: Inside Safe Zone");
startTrackingPointer(event.pointer, event.transform);
resolve(GestureDisposition.accepted);
stopTrackingPointer(event.pointer);
}
PageView Widget
PageView.builder(
itemCount: 5,
physics: CustomScrollPhysics(),
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => CustomGestureRecognizer(maxScreenOffsetX: screenWidth)),
].toSet(),
);
});
Screen Width
@override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
return Scaffold(//...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With