水平方向にスクロールするWebView
の中に、垂直方向にスクロールする PageView
があります。このようなもの:
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
にアップグレードした後で壊れました。これで、ジェスチャがほぼ完全に垂直である場合でも、水平スクロールが常に勝つようです。ページが垂直方向にスクロールされる場合、それはジェスチャーが停止した後(つまり、ジェスチャーの後に画面へのタッチを停止したとき)のみです-ジェスチャーの発生中に垂直方向のスクロールはありません。
VerticalDragGestureRecognizer
をgestureRecognizers
に追加したり削除したりしても、効果はありません。プログラムは、認識機能がリストにないかのように機能します(ただし、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
内にラップされているようです)が勝利を宣言することはありません。実際には完全に無視されているようです-VerticalDragGestureRecognizer
にgestureRecognizers
があり、それがない場合のジェスチャーアリーナ出力はまったく同じです。
これはFlutterのバグの可能性があるため、 FlutterのGitHubの問題 も作成しました。しかしどちらにしても、現在のバージョンのFlutterでこの効果を実現するにはどうすればよいですか?回避策や正規の解決策があれば、高く評価されます。
アリーナのルールが変わったようです。現在、アリーナは、アクティブなレシーバーを持つジェスチャーの勝利を宣言しています。これにより、ジェスチャーの応答性がさらに向上します。ただし、ネイティブビューはジェスチャーを要求せず、他のアクティブな検出器/受信者がジェスチャーを要求しない場合にのみジェスチャーを消費するため、垂直方向のドラッグは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
で新しいクラスを使用できます。
PageView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return WebView(
initialUrl: 'https://flutter.dev/docs',
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
);
},
);
私がSDKをアップグレードしたのは、あなたが説明したこの問題が発生するためだけです。この問題は非常に煩わしく、私はこのやや醜いハックを思いつきました。
このCustomGestureRecognizer
は、イベントが中央(通常はスクロールする場所)で発生しているときに、不要な動作を無視します。これには、私が処理できると私が信じるいくつかのオーバースクロールシャドウが付属していますが、それだけの時間がかかる可能性があります。
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ウィジェット
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(//...