質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

Q&A

解決済

1回答

2334閲覧

InteractiveViewerで元の画面サイズより広く表示した領域で、子Widgetの位置を移動させたい

grief137

総合スコア14

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

0グッド

1クリップ

投稿2020/08/10 06:27

編集2020/08/18 15:13

実現したいこと

①InteractiveViewerで元の画面サイズより広げた領域で、子Widgetの位置をドラッグで動かしたい。
②親Widgetの位置をドラッグで移動した際に、子Widgetの位置も追従するようにしたい。

以下のisuueより、GestureDetectorは自身&親の領域から外れた箇所では反応しない為Overlayを利用して子Widgetを表示しました。
https://github.com/flutter/flutter/issues/27587
(Overlayの利用には以下記事を参考)
https://medium.com/@billyleverington/building-instagrams-pinch-zoom-and-drag-a-photo-in-flutter-110f29a79bb7

しかし、子Widgetが1回目はドラッグによって位置を変更できますが、2回目のドラッグしようとするとGestureDetectorが反応せず、背景のInteractiveViewerのGestureDetectorが反応してしまいます。
(そして子Widgetの位置が親(InteractiveViewer)についていかない...)

解決方法が検討つかず、何か良い方法はないかとアドバイスを頂きたく投稿しました。
どうぞよろしくお願い致します。

2020/08/19追記

OverlayEntryを手放すしていなかった為、ZoomOverlayのdisposeメソッドでOverlayEntry.remove()を追加。
子Widgetが親についていくようになりました。
(2回目ドラッグが効かない問題は解消していない)

試したこと

子Widgetに以下パターンを試しました。
①Overlayを利用:2回目にGestureDetectorが反応しない
②estureDetectorのみ利用:元の画面サイズを超える領域に表示できない
③InteractiveViewerを利用:2回目にGestureDetectorが反応しない

現状の動作 (2020/08/19現在)

Overlayを使用した場合の挙動です。
イメージ説明

ソースコード

// InfiniteScreen4.dart class InfiniteScreen4 extends StatefulWidget { InfiniteScreen4({Key key}) : super(key: key); @override _InfiniteScreenState createState() => _InfiniteScreenState(); } class _InfiniteScreenState extends State<InfiniteScreen4> { final TransformationController _controller = TransformationController(); Matrix4 _matrix; double _dx = 100; double _dy = 100; Widget _buildInteractiveViewerTwice(Size viewportSize) { return ClipRect( child: InteractiveViewer( transformationController: _controller, boundaryMargin: EdgeInsets.symmetric( horizontal: viewportSize.width, vertical: viewportSize.height), child: Container( color: Colors.green, child: Stack( children: [ InteractiveViewer( boundaryMargin: EdgeInsets.symmetric(vertical: 1000, horizontal: 1000), child: Container( width: 50, height: 50, color: Colors.blue, )), ], ), )), ); } Widget _buildInteractiveViewerAndOverlay(Size viewportSize) { return ClipRect( child: InteractiveViewer( transformationController: _controller, boundaryMargin: EdgeInsets.symmetric( horizontal: viewportSize.width, vertical: viewportSize.height), child: Container( color: Colors.green, child: Stack( children: [ ZoomOverlay( child: Container( width: 50, height: 50, color: Colors.blue, )), ], ), )), ); } Widget _buildInteractiveViewerAndGestureDetector(Size viewportSize) { return ClipRect( child: InteractiveViewer( transformationController: _controller, boundaryMargin: EdgeInsets.symmetric( horizontal: viewportSize.width, vertical: viewportSize.height), child: Container( color: Colors.green, child: Stack( children: [ Positioned.fromRect( rect: Rect.fromCenter( center: Offset(_dx, _dy), width: 50, height: 50, ), child: GestureDetector( onPanUpdate: (details) { setState(() { _dx = _dx + details.delta.dx; _dy = _dy + details.delta.dy; }); }, child: Container( color: Colors.blue, )), ), ], ), )), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Infinite Screen4')), body: Container( color: Colors.grey, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: LayoutBuilder( builder: (context, constraints) { final viewportSize = Size(constraints.maxWidth, constraints.maxHeight); if (_matrix != null) { _matrix = Matrix4.identity() ..translate( viewportSize.width / 2, viewportSize.height / 2, ); _controller.value = _matrix; } return _buildInteractiveViewerTwice(viewportSize); // return _buildInteractiveViewerAndOverlay(viewportSize); // return _buildInteractiveViewerAndGestureDetector(viewportSize); }, ), ), ); } }
// ZoomOverlay.dart import 'package:flutter/material.dart'; import 'package:vector_math/vector_math_64.dart' as math; class TransformWidget extends StatefulWidget { final Widget child; final Matrix4 matrix; const TransformWidget({Key key, @required this.child, @required this.matrix}) : super(key: key); @override _TransformWidgetState createState() => _TransformWidgetState(); } class _TransformWidgetState extends State<TransformWidget> { Matrix4 _matrix = Matrix4.identity(); @override Widget build(BuildContext context) { return _matrix != null ? Transform( transform: (widget.matrix * _matrix), child: widget.child, ) : Container(); } void setMatrix(Matrix4 matrix) { setState(() { _matrix = matrix; }); } } class ZoomOverlay extends StatefulWidget { final Widget child; const ZoomOverlay({ Key key, @required this.child, }) : super(key: key); @override _ZoomOverlayState createState() => _ZoomOverlayState(); } class _ZoomOverlayState extends State<ZoomOverlay> with TickerProviderStateMixin { Matrix4 _matrix = Matrix4.identity(); Offset _startFocalPoint; OverlayEntry _overlayEntry; bool _isZooming = false; Matrix4 _transformMatrix = Matrix4.identity(); double _x = 100; double _y = 100; final GlobalKey<_TransformWidgetState> _transformWidget = GlobalKey<_TransformWidgetState>(); @override void dispose() { _overlayEntry.remove(); _overlayEntry = null; super.dispose(); } @override Widget build(BuildContext context) { return Positioned.fromRect( rect: Rect.fromCenter( center: Offset(_x, _y), width: 50, height: 50, ), child: GestureDetector( onTap: () { print('on tap!!!!!'); }, onScaleStart: (details) { onScaleStart(details, context); }, onScaleUpdate: onScaleUpdate, child: Opacity( opacity: _isZooming ? 0 : 1, child: widget.child, ), ), ); } void onScaleStart(ScaleStartDetails details, BuildContext context) { _startFocalPoint = details.focalPoint; _matrix = Matrix4.identity(); RenderBox renderBox = context.findRenderObject(); Offset position = renderBox.localToGlobal(Offset.zero); _transformMatrix = Matrix4.translation(math.Vector3(position.dx, position.dy, 0)); show(context); setState(() { _isZooming = true; }); } void onScaleUpdate(ScaleUpdateDetails details) { if (!_isZooming) return; Offset translationDelta = details.focalPoint - _startFocalPoint; Matrix4 translate = Matrix4.translation( math.Vector3(translationDelta.dx, translationDelta.dy, 0)); RenderBox renderBox = context.findRenderObject(); Offset focalPoint = renderBox.globalToLocal(details.focalPoint - translationDelta); var dx = (1 - details.scale) * focalPoint.dx; var dy = (1 - details.scale) * focalPoint.dy; Matrix4 scale = Matrix4(details.scale, 0, 0, 0, 0, details.scale, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1); _matrix = translate * scale; print(details.localFocalPoint); setState(() { _x = details.localFocalPoint.dx; _y = details.localFocalPoint.dy; }); if (_transformWidget != null && _transformWidget.currentState != null) { _transformWidget.currentState.setMatrix(_matrix); } } Widget _build(BuildContext context) { return IgnorePointer( child: Stack( children: [ TransformWidget( key: _transformWidget, child: widget.child, matrix: _transformMatrix), ], ), ); } void show(BuildContext context) { if (!_isZooming) { OverlayState overlayState = Overlay.of(context); _overlayEntry = OverlayEntry(builder: _build); overlayState.insert(_overlayEntry); } } }

補足情報

Flutter 1.20.0 • channel stable
Dart 2.9.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

satokei

2020/09/15 11:40 編集

「③InteractiveViewerを利用」を試して分かったことは、 子Widget(青)を動かした後でも子Widgetをドラッグするためのジェスチャー領域は動いていないようです。 子Widgetを動かした後、最初に子Widgetがあった領域をドラッグすると、子Widgetを動かすことができました。
guest

回答1

0

ベストアンサー

②GestureDetectorのみ利用:元の画面サイズを超える領域に表示できない

質問のコードのGestureDetectorを使用するパターンを変えて、
Positionedの代わりにTransformを使用すると期待通りに動作すると思います。

dart

1 Widget _buildInteractiveViewerAndGestureDetector(Size viewportSize) { 2 return ClipRect( 3 child: InteractiveViewer( 4 transformationController: _controller, 5 boundaryMargin: EdgeInsets.symmetric( 6 horizontal: viewportSize.width, vertical: viewportSize.height), 7 child: Container( 8 color: Colors.green, 9 child: Stack( 10 children: [ 11 Transform( 12 transform: Matrix4.translationValues(_dx, _dy, 0), 13 child: GestureDetector( 14 onPanUpdate: (details) { 15 setState(() { 16 _dx = _dx + details.delta.dx; 17 _dy = _dy + details.delta.dy; 18 }); 19 }, 20 child: Container( 21 width: 50, 22 height: 50, 23 color: Colors.blue, 24 )), 25 ), 26 ], 27 ), 28 )), 29 ); 30 } 31

ちなみに、InteractiveViewerを入れ子にすると期待通りに動作しない原因は、
内部構造としてGestureDetectorTransformの外側にあるためのようです。

https://github.com/flutter/flutter/blob/1.20.0/packages/flutter/lib/src/widgets/interactive_viewer.dart#L972

投稿2020/09/23 17:30

satokei

総合スコア1204

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

grief137

2020/09/24 12:23

返信遅くなってすみません、ご回答ありがとうございます! 確かに元画面のサイズを超えた領域でもドラックできることを確認できました。 自分ではOverlayでどうにか進めてましたが、Overlayを使用しなくて済む点も本当にありがたいです!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問