水平滚动与 WebView 结合时的滚动优先级
Scrolling priority when combining horizontal scrolling with WebView
我在水平滚动 PageView
中有一个垂直滚动 WebView
。像这样:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => VerticalDragGestureRecognizer()),
].toSet(),
);
},
);
对于之前稳定版本的 Flutter (1.5.4),这按预期工作 - 垂直滚动将移动 WebView 内的内容,水平滚动将移动 PageView。
升级到 Flutter 后出现问题 v1.7.8+hotfix.3
。现在水平滚动似乎总是赢,即使手势非常明显几乎完全垂直。如果页面完全垂直滚动,那只是在手势停止后(即,当我在手势后停止触摸屏幕时)——手势发生时没有垂直滚动。
从 gestureRecognizers
添加和删除 VerticalDragGestureRecognizer
现在没有效果 - 无论哪种方式,程序都像识别器不在列表中一样工作(尽管并不是 gestureRecognizers
完全忽略,因为添加 EagerGestureRecognizer
确实有效果)。
这是手势竞技场的调试输出(请记住,我试图让我的手势尽可能保持垂直,但即使是轻微的手指向两侧移动也足以让 HorizontalDragGestureRecognizer
赢了,尽管我也一直在垂直移动):
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)
这就是当您设法保持手势完全垂直时会发生的情况(在使用鼠标的模拟器上似乎更容易),而拖动手势正在进行中:
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.
即使是轻微的垂直移动也会使 HorizontalDragGestureRecognizer
宣告胜利,但 VerticalDragGestureRecognizer
(似乎被包裹在 _CombiningGestureArenaMember
中)永远不会宣布胜利。事实上,它似乎完全被忽略了——在 gestureRecognizers
中使用 VerticalDragGestureRecognizer
和不使用它的手势竞技场输出是完全相同的。
这可能是 Flutter 的一个错误,所以我也创建了 an issue on Flutter's GitHub。但无论哪种方式——如何使用当前版本的 Flutter 实现这种效果?任何解决方法或规范解决方案将不胜感激。
我升级了我的 sdk 只是为了遇到你描述的这个问题。这个问题太烦人了,我想出了这个相当丑陋的黑客。
当事件发生在中间(通常是我们滚动的地方)时,这个 CustomGestureRecognizer
将忽略不需要的行为。
这确实带有一些我认为可以处理的 over-scrolling 阴影,可能需要更多时间。
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.builder(
itemCount: 5,
physics: CustomScrollPhysics(),
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => CustomGestureRecognizer(maxScreenOffsetX: screenWidth)),
].toSet(),
);
});
屏幕宽度
@override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
return Scaffold(//...
看来江湖规则变了。现在,竞技场宣布拥有主动接收者的手势获胜。这确实进一步提高了手势的响应能力。但是,由于本机视图不声明手势并且仅在没有其他活动 detector/receiver 声明它们时才使用它们,我怀疑垂直拖动甚至没有作为 WebView 的手势进入竞技场。这就是为什么任何轻微的水平拖动都会导致水平拖动手势获胜 - 因为根本没有其他小部件声明任何手势。
您可以扩展 VerticalDragGestureRecognizer
,因此它接受手势:
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) {}
}
之后,您可以在gestureRecognizers
中使用新的class:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
);
},
);
我在水平滚动 PageView
中有一个垂直滚动 WebView
。像这样:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => VerticalDragGestureRecognizer()),
].toSet(),
);
},
);
对于之前稳定版本的 Flutter (1.5.4),这按预期工作 - 垂直滚动将移动 WebView 内的内容,水平滚动将移动 PageView。
升级到 Flutter 后出现问题 v1.7.8+hotfix.3
。现在水平滚动似乎总是赢,即使手势非常明显几乎完全垂直。如果页面完全垂直滚动,那只是在手势停止后(即,当我在手势后停止触摸屏幕时)——手势发生时没有垂直滚动。
从 gestureRecognizers
添加和删除 VerticalDragGestureRecognizer
现在没有效果 - 无论哪种方式,程序都像识别器不在列表中一样工作(尽管并不是 gestureRecognizers
完全忽略,因为添加 EagerGestureRecognizer
确实有效果)。
这是手势竞技场的调试输出(请记住,我试图让我的手势尽可能保持垂直,但即使是轻微的手指向两侧移动也足以让 HorizontalDragGestureRecognizer
赢了,尽管我也一直在垂直移动):
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)
这就是当您设法保持手势完全垂直时会发生的情况(在使用鼠标的模拟器上似乎更容易),而拖动手势正在进行中:
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.
即使是轻微的垂直移动也会使 HorizontalDragGestureRecognizer
宣告胜利,但 VerticalDragGestureRecognizer
(似乎被包裹在 _CombiningGestureArenaMember
中)永远不会宣布胜利。事实上,它似乎完全被忽略了——在 gestureRecognizers
中使用 VerticalDragGestureRecognizer
和不使用它的手势竞技场输出是完全相同的。
这可能是 Flutter 的一个错误,所以我也创建了 an issue on Flutter's GitHub。但无论哪种方式——如何使用当前版本的 Flutter 实现这种效果?任何解决方法或规范解决方案将不胜感激。
我升级了我的 sdk 只是为了遇到你描述的这个问题。这个问题太烦人了,我想出了这个相当丑陋的黑客。
当事件发生在中间(通常是我们滚动的地方)时,这个 CustomGestureRecognizer
将忽略不需要的行为。
这确实带有一些我认为可以处理的 over-scrolling 阴影,可能需要更多时间。
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.builder(
itemCount: 5,
physics: CustomScrollPhysics(),
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => CustomGestureRecognizer(maxScreenOffsetX: screenWidth)),
].toSet(),
);
});
屏幕宽度
@override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
return Scaffold(//...
看来江湖规则变了。现在,竞技场宣布拥有主动接收者的手势获胜。这确实进一步提高了手势的响应能力。但是,由于本机视图不声明手势并且仅在没有其他活动 detector/receiver 声明它们时才使用它们,我怀疑垂直拖动甚至没有作为 WebView 的手势进入竞技场。这就是为什么任何轻微的水平拖动都会导致水平拖动手势获胜 - 因为根本没有其他小部件声明任何手势。
您可以扩展 VerticalDragGestureRecognizer
,因此它接受手势:
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) {}
}
之后,您可以在gestureRecognizers
中使用新的class:
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
);
},
);