実現したいこと
①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現在)
ソースコード
// 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
回答1件
あなたの回答
tips
プレビュー