diff --git a/examples/widgets/drag_and_drop.dart b/examples/widgets/drag_and_drop.dart index 02d21e548d..70b68ef38d 100644 --- a/examples/widgets/drag_and_drop.dart +++ b/examples/widgets/drag_and_drop.dart @@ -50,13 +50,15 @@ class ExampleDragTargetState extends State { } class Dot extends StatelessComponent { - Dot({ Key key, this.color }): super(key: key); + Dot({ Key key, this.color, this.size }): super(key: key); final Color color; + final double size; Widget build(BuildContext context) { return new Container( - width: 50.0, - height: 50.0, + width: size, + height: size, decoration: new BoxDecoration( + borderRadius: 10.0, backgroundColor: color ) ); @@ -68,12 +70,24 @@ class ExampleDragSource extends StatelessComponent { final NavigatorState navigator; final String name; final Color color; + + static const kDotSize = 50.0; + static const kFingerSize = 50.0; + Widget build(BuildContext context) { return new Draggable( navigator: navigator, data: new DragData(name), - child: new Dot(color: color), - feedback: new Dot(color: color) + child: new Dot(color: color, size: kDotSize), + feedback: new Transform( + transform: new Matrix4.identity()..translate(-kDotSize / 2.0, -(kDotSize / 2.0 + kFingerSize)), + child: new Opacity( + opacity: 0.75, + child: new Dot(color: color, size: kDotSize) + ) + ), + feedbackOffset: const Offset(0.0, -kFingerSize), + dragAnchor: DragAnchor.pointer ); } } diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index f5552372a0..19f372cf6d 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -17,9 +17,38 @@ typedef void DragTargetAccept(T data); typedef Widget DragTargetBuilder(BuildContext context, List candidateData, List rejectedData); typedef void DragFinishedNotification(); +enum DragAnchor { + /// Display the feedback anchored at the position of the original child. If + /// feedback is identical to the child, then this means the feedback will + /// exactly overlap the original child when the drag starts. + child, + + /// Display the feedback anchored at the position of the touch that started + /// the drag. If feedback is identical to the child, then this means the top + /// left of the feedback will be under the finger when the drag starts. This + /// will likely not exactly overlap the original child, e.g. if the child is + /// big and the touch was not centered. This mode is useful when the feedback + /// is transformed so as to move the feedback to the left by half its width, + /// and up by half its width plus the height of the finger, since then it + /// appears as if putting the finger down makes the touch feedback appear + /// above the finger. (It feels weird for it to appear offset from the + /// original child if it's anchored to the child and not the finger.) + pointer, +} + class Draggable extends StatefulComponent { - Draggable({ Key key, this.navigator, this.data, this.child, this.feedback }): super(key: key) { + Draggable({ + Key key, + this.navigator, + this.data, + this.child, + this.feedback, + this.feedbackOffset: Offset.zero, + this.dragAnchor: DragAnchor.child + }): super(key: key) { assert(navigator != null); + assert(child != null); + assert(feedback != null); } final NavigatorState navigator; @@ -27,6 +56,12 @@ class Draggable extends StatefulComponent { final Widget child; final Widget feedback; + /// The feedbackOffset can be used to set the hit test target point for the + /// purposes of finding a drag target. It is especially useful if the feedback + /// is transformed compared to the child. + final Offset feedbackOffset; + final DragAnchor dragAnchor; + DraggableState createState() => new DraggableState(); } @@ -36,12 +71,23 @@ class DraggableState extends State { void _startDrag(sky.PointerEvent event) { if (_route != null) return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the route so it can do everything itself. then we can have multiple drags at the same time. - Point point = new Point(event.x, event.y); - RenderBox renderObject = context.findRenderObject(); + final Point point = new Point(event.x, event.y); + Point dragStartPoint; + switch (config.dragAnchor) { + case DragAnchor.child: + final RenderBox renderObject = context.findRenderObject(); + dragStartPoint = renderObject.globalToLocal(point); + break; + case DragAnchor.pointer: + dragStartPoint = Point.origin; + break; + } + assert(dragStartPoint != null); _route = new DragRoute( data: config.data, - dragStartPoint: renderObject.globalToLocal(point), + dragStartPoint: dragStartPoint, feedback: config.feedback, + feedbackOffset: config.feedbackOffset, onDragFinished: () { _route = null; } @@ -149,11 +195,20 @@ class DragTargetState extends State> { enum DragEndKind { dropped, canceled } class DragRoute extends Route { - DragRoute({ this.data, this.dragStartPoint: Point.origin, this.feedback, this.onDragFinished }); + DragRoute({ + this.data, + this.dragStartPoint: Point.origin, + this.feedback, + this.feedbackOffset: Offset.zero, + this.onDragFinished + }) { + assert(feedbackOffset != null); + } final dynamic data; final Point dragStartPoint; final Widget feedback; + final Offset feedbackOffset; final DragFinishedNotification onDragFinished; DragTargetState _activeTarget; @@ -162,7 +217,7 @@ class DragRoute extends Route { void update(Point globalPosition) { _lastOffset = globalPosition - dragStartPoint; - HitTestResult result = WidgetFlutterBinding.instance.hitTest(globalPosition); + HitTestResult result = WidgetFlutterBinding.instance.hitTest(globalPosition + feedbackOffset); DragTargetState target = _getDragTarget(result.path); if (target == _activeTarget) return; @@ -208,10 +263,7 @@ class DragRoute extends Route { left: _lastOffset.dx, top: _lastOffset.dy, child: new IgnorePointer( - child: new Opacity( - opacity: 0.5, - child: feedback - ) + child: feedback ) ); }