"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/widgets/drag_target.dart" (13 Nov 2020, 28404 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'package:flutter/gestures.dart';
    8 import 'package:flutter/rendering.dart';
    9 import 'package:flutter/services.dart';
   10 
   11 import 'basic.dart';
   12 import 'binding.dart';
   13 import 'framework.dart';
   14 import 'overlay.dart';
   15 
   16 /// Signature for determining whether the given data will be accepted by a [DragTarget].
   17 ///
   18 /// Used by [DragTarget.onWillAccept].
   19 typedef DragTargetWillAccept<T> = bool Function(T data);
   20 
   21 /// Signature for causing a [DragTarget] to accept the given data.
   22 ///
   23 /// Used by [DragTarget.onAccept].
   24 typedef DragTargetAccept<T> = void Function(T data);
   25 
   26 /// Signature for determining information about the acceptance by a [DragTarget].
   27 ///
   28 /// Used by [DragTarget.onAcceptWithDetails].
   29 typedef DragTargetAcceptWithDetails<T> = void Function(DragTargetDetails<T> details);
   30 
   31 /// Signature for building children of a [DragTarget].
   32 ///
   33 /// The `candidateData` argument contains the list of drag data that is hovering
   34 /// over this [DragTarget] and that has passed [DragTarget.onWillAccept]. The
   35 /// `rejectedData` argument contains the list of drag data that is hovering over
   36 /// this [DragTarget] and that will not be accepted by the [DragTarget].
   37 ///
   38 /// Used by [DragTarget.builder].
   39 typedef DragTargetBuilder<T> = Widget Function(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
   40 
   41 /// Signature for when a [Draggable] is dropped without being accepted by a [DragTarget].
   42 ///
   43 /// Used by [Draggable.onDraggableCanceled].
   44 typedef DraggableCanceledCallback = void Function(Velocity velocity, Offset offset);
   45 
   46 /// Signature for when the draggable is dropped.
   47 ///
   48 /// The velocity and offset at which the pointer was moving when the draggable
   49 /// was dropped is available in the [DraggableDetails]. Also included in the
   50 /// `details` is whether the draggable's [DragTarget] accepted it.
   51 ///
   52 /// Used by [Draggable.onDragEnd]
   53 typedef DragEndCallback = void Function(DraggableDetails details);
   54 
   55 /// Signature for when a [Draggable] leaves a [DragTarget].
   56 ///
   57 /// Used by [DragTarget.onLeave].
   58 typedef DragTargetLeave = void Function(Object data);
   59 
   60 /// Signature for when a [Draggable] moves within a [DragTarget].
   61 ///
   62 /// Used by [DragTarget.onMove].
   63 typedef DragTargetMove = void Function(DragTargetDetails<dynamic> details);
   64 
   65 /// Where the [Draggable] should be anchored during a drag.
   66 enum DragAnchor {
   67   /// Display the feedback anchored at the position of the original child. If
   68   /// feedback is identical to the child, then this means the feedback will
   69   /// exactly overlap the original child when the drag starts.
   70   child,
   71 
   72   /// Display the feedback anchored at the position of the touch that started
   73   /// the drag. If feedback is identical to the child, then this means the top
   74   /// left of the feedback will be under the finger when the drag starts. This
   75   /// will likely not exactly overlap the original child, e.g. if the child is
   76   /// big and the touch was not centered. This mode is useful when the feedback
   77   /// is transformed so as to move the feedback to the left by half its width,
   78   /// and up by half its width plus the height of the finger, since then it
   79   /// appears as if putting the finger down makes the touch feedback appear
   80   /// above the finger. (It feels weird for it to appear offset from the
   81   /// original child if it's anchored to the child and not the finger.)
   82   pointer,
   83 }
   84 
   85 /// A widget that can be dragged from to a [DragTarget].
   86 ///
   87 /// When a draggable widget recognizes the start of a drag gesture, it displays
   88 /// a [feedback] widget that tracks the user's finger across the screen. If the
   89 /// user lifts their finger while on top of a [DragTarget], that target is given
   90 /// the opportunity to accept the [data] carried by the draggable.
   91 ///
   92 /// On multitouch devices, multiple drags can occur simultaneously because there
   93 /// can be multiple pointers in contact with the device at once. To limit the
   94 /// number of simultaneous drags, use the [maxSimultaneousDrags] property. The
   95 /// default is to allow an unlimited number of simultaneous drags.
   96 ///
   97 /// This widget displays [child] when zero drags are under way. If
   98 /// [childWhenDragging] is non-null, this widget instead displays
   99 /// [childWhenDragging] when one or more drags are underway. Otherwise, this
  100 /// widget always displays [child].
  101 ///
  102 /// {@youtube 560 315 https://www.youtube.com/watch?v=QzA4c4QHZCY}
  103 ///
  104 /// See also:
  105 ///
  106 ///  * [DragTarget]
  107 ///  * [LongPressDraggable]
  108 class Draggable<T> extends StatefulWidget {
  109   /// Creates a widget that can be dragged to a [DragTarget].
  110   ///
  111   /// The [child] and [feedback] arguments must not be null. If
  112   /// [maxSimultaneousDrags] is non-null, it must be non-negative.
  113   const Draggable({
  114     Key key,
  115     @required this.child,
  116     @required this.feedback,
  117     this.data,
  118     this.axis,
  119     this.childWhenDragging,
  120     this.feedbackOffset = Offset.zero,
  121     this.dragAnchor = DragAnchor.child,
  122     this.affinity,
  123     this.maxSimultaneousDrags,
  124     this.onDragStarted,
  125     this.onDraggableCanceled,
  126     this.onDragEnd,
  127     this.onDragCompleted,
  128     this.ignoringFeedbackSemantics = true,
  129   }) : assert(child != null),
  130        assert(feedback != null),
  131        assert(ignoringFeedbackSemantics != null),
  132        assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
  133        super(key: key);
  134 
  135 
  136   /// The data that will be dropped by this draggable.
  137   final T data;
  138 
  139   /// The [Axis] to restrict this draggable's movement, if specified.
  140   ///
  141   /// When axis is set to [Axis.horizontal], this widget can only be dragged
  142   /// horizontally. Behavior is similar for [Axis.vertical].
  143   ///
  144   /// Defaults to allowing drag on both [Axis.horizontal] and [Axis.vertical].
  145   ///
  146   /// When null, allows drag on both [Axis.horizontal] and [Axis.vertical].
  147   ///
  148   /// For the direction of gestures this widget competes with to start a drag
  149   /// event, see [Draggable.affinity].
  150   final Axis axis;
  151 
  152   /// The widget below this widget in the tree.
  153   ///
  154   /// This widget displays [child] when zero drags are under way. If
  155   /// [childWhenDragging] is non-null, this widget instead displays
  156   /// [childWhenDragging] when one or more drags are underway. Otherwise, this
  157   /// widget always displays [child].
  158   ///
  159   /// The [feedback] widget is shown under the pointer when a drag is under way.
  160   ///
  161   /// To limit the number of simultaneous drags on multitouch devices, see
  162   /// [maxSimultaneousDrags].
  163   ///
  164   /// {@macro flutter.widgets.child}
  165   final Widget child;
  166 
  167   /// The widget to display instead of [child] when one or more drags are under way.
  168   ///
  169   /// If this is null, then this widget will always display [child] (and so the
  170   /// drag source representation will not change while a drag is under
  171   /// way).
  172   ///
  173   /// The [feedback] widget is shown under the pointer when a drag is under way.
  174   ///
  175   /// To limit the number of simultaneous drags on multitouch devices, see
  176   /// [maxSimultaneousDrags].
  177   final Widget childWhenDragging;
  178 
  179   /// The widget to show under the pointer when a drag is under way.
  180   ///
  181   /// See [child] and [childWhenDragging] for information about what is shown
  182   /// at the location of the [Draggable] itself when a drag is under way.
  183   final Widget feedback;
  184 
  185   /// The feedbackOffset can be used to set the hit test target point for the
  186   /// purposes of finding a drag target. It is especially useful if the feedback
  187   /// is transformed compared to the child.
  188   final Offset feedbackOffset;
  189 
  190   /// Where this widget should be anchored during a drag.
  191   final DragAnchor dragAnchor;
  192 
  193   /// Whether the semantics of the [feedback] widget is ignored when building
  194   /// the semantics tree.
  195   ///
  196   /// This value should be set to false when the [feedback] widget is intended
  197   /// to be the same object as the [child].  Placing a [GlobalKey] on this
  198   /// widget will ensure semantic focus is kept on the element as it moves in
  199   /// and out of the feedback position.
  200   ///
  201   /// Defaults to true.
  202   final bool ignoringFeedbackSemantics;
  203 
  204   /// Controls how this widget competes with other gestures to initiate a drag.
  205   ///
  206   /// If affinity is null, this widget initiates a drag as soon as it recognizes
  207   /// a tap down gesture, regardless of any directionality. If affinity is
  208   /// horizontal (or vertical), then this widget will compete with other
  209   /// horizontal (or vertical, respectively) gestures.
  210   ///
  211   /// For example, if this widget is placed in a vertically scrolling region and
  212   /// has horizontal affinity, pointer motion in the vertical direction will
  213   /// result in a scroll and pointer motion in the horizontal direction will
  214   /// result in a drag. Conversely, if the widget has a null or vertical
  215   /// affinity, pointer motion in any direction will result in a drag rather
  216   /// than in a scroll because the draggable widget, being the more specific
  217   /// widget, will out-compete the [Scrollable] for vertical gestures.
  218   ///
  219   /// For the directions this widget can be dragged in after the drag event
  220   /// starts, see [Draggable.axis].
  221   final Axis affinity;
  222 
  223   /// How many simultaneous drags to support.
  224   ///
  225   /// When null, no limit is applied. Set this to 1 if you want to only allow
  226   /// the drag source to have one item dragged at a time. Set this to 0 if you
  227   /// want to prevent the draggable from actually being dragged.
  228   ///
  229   /// If you set this property to 1, consider supplying an "empty" widget for
  230   /// [childWhenDragging] to create the illusion of actually moving [child].
  231   final int maxSimultaneousDrags;
  232 
  233   /// Called when the draggable starts being dragged.
  234   final VoidCallback onDragStarted;
  235 
  236   /// Called when the draggable is dropped without being accepted by a [DragTarget].
  237   ///
  238   /// This function might be called after this widget has been removed from the
  239   /// tree. For example, if a drag was in progress when this widget was removed
  240   /// from the tree and the drag ended up being canceled, this callback will
  241   /// still be called. For this reason, implementations of this callback might
  242   /// need to check [State.mounted] to check whether the state receiving the
  243   /// callback is still in the tree.
  244   final DraggableCanceledCallback onDraggableCanceled;
  245 
  246   /// Called when the draggable is dropped and accepted by a [DragTarget].
  247   ///
  248   /// This function might be called after this widget has been removed from the
  249   /// tree. For example, if a drag was in progress when this widget was removed
  250   /// from the tree and the drag ended up completing, this callback will
  251   /// still be called. For this reason, implementations of this callback might
  252   /// need to check [State.mounted] to check whether the state receiving the
  253   /// callback is still in the tree.
  254   final VoidCallback onDragCompleted;
  255 
  256   /// Called when the draggable is dropped.
  257   ///
  258   /// The velocity and offset at which the pointer was moving when it was
  259   /// dropped is available in the [DraggableDetails]. Also included in the
  260   /// `details` is whether the draggable's [DragTarget] accepted it.
  261   ///
  262   /// This function will only be called while this widget is still mounted to
  263   /// the tree (i.e. [State.mounted] is true).
  264   final DragEndCallback onDragEnd;
  265 
  266   /// Creates a gesture recognizer that recognizes the start of the drag.
  267   ///
  268   /// Subclasses can override this function to customize when they start
  269   /// recognizing a drag.
  270   @protected
  271   MultiDragGestureRecognizer<MultiDragPointerState> createRecognizer(GestureMultiDragStartCallback onStart) {
  272     switch (affinity) {
  273       case Axis.horizontal:
  274         return HorizontalMultiDragGestureRecognizer()..onStart = onStart;
  275       case Axis.vertical:
  276         return VerticalMultiDragGestureRecognizer()..onStart = onStart;
  277     }
  278     return ImmediateMultiDragGestureRecognizer()..onStart = onStart;
  279   }
  280 
  281   @override
  282   _DraggableState<T> createState() => _DraggableState<T>();
  283 }
  284 
  285 /// Makes its child draggable starting from long press.
  286 class LongPressDraggable<T> extends Draggable<T> {
  287   /// Creates a widget that can be dragged starting from long press.
  288   ///
  289   /// The [child] and [feedback] arguments must not be null. If
  290   /// [maxSimultaneousDrags] is non-null, it must be non-negative.
  291   const LongPressDraggable({
  292     Key key,
  293     @required Widget child,
  294     @required Widget feedback,
  295     T data,
  296     Axis axis,
  297     Widget childWhenDragging,
  298     Offset feedbackOffset = Offset.zero,
  299     DragAnchor dragAnchor = DragAnchor.child,
  300     int maxSimultaneousDrags,
  301     VoidCallback onDragStarted,
  302     DraggableCanceledCallback onDraggableCanceled,
  303     DragEndCallback onDragEnd,
  304     VoidCallback onDragCompleted,
  305     this.hapticFeedbackOnStart = true,
  306     bool ignoringFeedbackSemantics = true,
  307   }) : super(
  308     key: key,
  309     child: child,
  310     feedback: feedback,
  311     data: data,
  312     axis: axis,
  313     childWhenDragging: childWhenDragging,
  314     feedbackOffset: feedbackOffset,
  315     dragAnchor: dragAnchor,
  316     maxSimultaneousDrags: maxSimultaneousDrags,
  317     onDragStarted: onDragStarted,
  318     onDraggableCanceled: onDraggableCanceled,
  319     onDragEnd: onDragEnd,
  320     onDragCompleted: onDragCompleted,
  321     ignoringFeedbackSemantics: ignoringFeedbackSemantics,
  322   );
  323 
  324   /// Whether haptic feedback should be triggered on drag start.
  325   final bool hapticFeedbackOnStart;
  326 
  327   @override
  328   DelayedMultiDragGestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
  329     return DelayedMultiDragGestureRecognizer()
  330       ..onStart = (Offset position) {
  331         final Drag result = onStart(position);
  332         if (result != null && hapticFeedbackOnStart)
  333           HapticFeedback.selectionClick();
  334         return result;
  335       };
  336   }
  337 }
  338 
  339 class _DraggableState<T> extends State<Draggable<T>> {
  340   @override
  341   void initState() {
  342     super.initState();
  343     _recognizer = widget.createRecognizer(_startDrag);
  344   }
  345 
  346   @override
  347   void dispose() {
  348     _disposeRecognizerIfInactive();
  349     super.dispose();
  350   }
  351 
  352   // This gesture recognizer has an unusual lifetime. We want to support the use
  353   // case of removing the Draggable from the tree in the middle of a drag. That
  354   // means we need to keep this recognizer alive after this state object has
  355   // been disposed because it's the one listening to the pointer events that are
  356   // driving the drag.
  357   //
  358   // We achieve that by keeping count of the number of active drags and only
  359   // disposing the gesture recognizer after (a) this state object has been
  360   // disposed and (b) there are no more active drags.
  361   GestureRecognizer _recognizer;
  362   int _activeCount = 0;
  363 
  364   void _disposeRecognizerIfInactive() {
  365     if (_activeCount > 0)
  366       return;
  367     _recognizer.dispose();
  368     _recognizer = null;
  369   }
  370 
  371   void _routePointer(PointerDownEvent event) {
  372     if (widget.maxSimultaneousDrags != null && _activeCount >= widget.maxSimultaneousDrags)
  373       return;
  374     _recognizer.addPointer(event);
  375   }
  376 
  377   _DragAvatar<T> _startDrag(Offset position) {
  378     if (widget.maxSimultaneousDrags != null && _activeCount >= widget.maxSimultaneousDrags)
  379       return null;
  380     Offset dragStartPoint;
  381     switch (widget.dragAnchor) {
  382       case DragAnchor.child:
  383         final RenderBox renderObject = context.findRenderObject() as RenderBox;
  384         dragStartPoint = renderObject.globalToLocal(position);
  385         break;
  386       case DragAnchor.pointer:
  387         dragStartPoint = Offset.zero;
  388         break;
  389     }
  390     setState(() {
  391       _activeCount += 1;
  392     });
  393     final _DragAvatar<T> avatar = _DragAvatar<T>(
  394       overlayState: Overlay.of(context, debugRequiredFor: widget),
  395       data: widget.data,
  396       axis: widget.axis,
  397       initialPosition: position,
  398       dragStartPoint: dragStartPoint,
  399       feedback: widget.feedback,
  400       feedbackOffset: widget.feedbackOffset,
  401       ignoringFeedbackSemantics: widget.ignoringFeedbackSemantics,
  402       onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) {
  403         if (mounted) {
  404           setState(() {
  405             _activeCount -= 1;
  406           });
  407         } else {
  408           _activeCount -= 1;
  409           _disposeRecognizerIfInactive();
  410         }
  411         if (mounted && widget.onDragEnd != null) {
  412           widget.onDragEnd(DraggableDetails(
  413               wasAccepted: wasAccepted,
  414               velocity: velocity,
  415               offset: offset,
  416           ));
  417         }
  418         if (wasAccepted && widget.onDragCompleted != null)
  419           widget.onDragCompleted();
  420         if (!wasAccepted && widget.onDraggableCanceled != null)
  421           widget.onDraggableCanceled(velocity, offset);
  422       },
  423     );
  424     if (widget.onDragStarted != null)
  425       widget.onDragStarted();
  426     return avatar;
  427   }
  428 
  429   @override
  430   Widget build(BuildContext context) {
  431     assert(Overlay.of(context, debugRequiredFor: widget) != null);
  432     final bool canDrag = widget.maxSimultaneousDrags == null ||
  433                          _activeCount < widget.maxSimultaneousDrags;
  434     final bool showChild = _activeCount == 0 || widget.childWhenDragging == null;
  435     return Listener(
  436       onPointerDown: canDrag ? _routePointer : null,
  437       child: showChild ? widget.child : widget.childWhenDragging,
  438     );
  439   }
  440 }
  441 
  442 /// Represents the details when a specific pointer event occurred on
  443 /// the [Draggable].
  444 ///
  445 /// This includes the [Velocity] at which the pointer was moving and [Offset]
  446 /// when the draggable event occurred, and whether its [DragTarget] accepted it.
  447 ///
  448 /// Also, this is the details object for callbacks that use [DragEndCallback].
  449 class DraggableDetails {
  450   /// Creates details for a [DraggableDetails].
  451   ///
  452   /// If [wasAccepted] is not specified, it will default to `false`.
  453   ///
  454   /// The [velocity] or [offset] arguments must not be `null`.
  455   DraggableDetails({
  456     this.wasAccepted = false,
  457     @required this.velocity,
  458     @required this.offset,
  459   }) : assert(velocity != null),
  460        assert(offset != null);
  461 
  462   /// Determines whether the [DragTarget] accepted this draggable.
  463   final bool wasAccepted;
  464 
  465   /// The velocity at which the pointer was moving when the specific pointer
  466   /// event occurred on the draggable.
  467   final Velocity velocity;
  468 
  469   /// The global position when the specific pointer event occurred on
  470   /// the draggable.
  471   final Offset offset;
  472 }
  473 
  474 /// Represents the details when a pointer event occurred on the [DragTarget].
  475 class DragTargetDetails<T> {
  476   /// Creates details for a [DragTarget] callback.
  477   ///
  478   /// The [offset] must not be null.
  479   DragTargetDetails({@required this.data, @required this.offset}) : assert(offset != null);
  480 
  481   /// The data that was dropped onto this [DragTarget].
  482   final T data;
  483 
  484   /// The global position when the specific pointer event occurred on
  485   /// the draggable.
  486   final Offset offset;
  487 }
  488 
  489 /// A widget that receives data when a [Draggable] widget is dropped.
  490 ///
  491 /// When a draggable is dragged on top of a drag target, the drag target is
  492 /// asked whether it will accept the data the draggable is carrying. If the user
  493 /// does drop the draggable on top of the drag target (and the drag target has
  494 /// indicated that it will accept the draggable's data), then the drag target is
  495 /// asked to accept the draggable's data.
  496 ///
  497 /// See also:
  498 ///
  499 ///  * [Draggable]
  500 ///  * [LongPressDraggable]
  501 class DragTarget<T> extends StatefulWidget {
  502   /// Creates a widget that receives drags.
  503   ///
  504   /// The [builder] argument must not be null.
  505   const DragTarget({
  506     Key key,
  507     @required this.builder,
  508     this.onWillAccept,
  509     this.onAccept,
  510     this.onAcceptWithDetails,
  511     this.onLeave,
  512     this.onMove,
  513   }) : super(key: key);
  514 
  515   /// Called to build the contents of this widget.
  516   ///
  517   /// The builder can build different widgets depending on what is being dragged
  518   /// into this drag target.
  519   final DragTargetBuilder<T> builder;
  520 
  521   /// Called to determine whether this widget is interested in receiving a given
  522   /// piece of data being dragged over this drag target.
  523   ///
  524   /// Called when a piece of data enters the target. This will be followed by
  525   /// either [onAccept] and [onAcceptWithDetails], if the data is dropped, or
  526   /// [onLeave], if the drag leaves the target.
  527   final DragTargetWillAccept<T> onWillAccept;
  528 
  529   /// Called when an acceptable piece of data was dropped over this drag target.
  530   ///
  531   /// Equivalent to [onAcceptWithDetails], but only includes the data.
  532   final DragTargetAccept<T> onAccept;
  533 
  534   /// Called when an acceptable piece of data was dropped over this drag target.
  535   ///
  536   /// Equivalent to [onAccept], but with information, including the data, in a
  537   /// [DragTargetDetails].
  538   final DragTargetAcceptWithDetails<T> onAcceptWithDetails;
  539 
  540   /// Called when a given piece of data being dragged over this target leaves
  541   /// the target.
  542   final DragTargetLeave onLeave;
  543 
  544   /// Called when a [Draggable] moves within this [DragTarget].
  545   ///
  546   /// Note that this includes entering and leaving the target.
  547   final DragTargetMove onMove;
  548 
  549   @override
  550   _DragTargetState<T> createState() => _DragTargetState<T>();
  551 }
  552 
  553 List<T> _mapAvatarsToData<T>(List<_DragAvatar<T>> avatars) {
  554   return avatars.map<T>((_DragAvatar<T> avatar) => avatar.data).toList();
  555 }
  556 
  557 class _DragTargetState<T> extends State<DragTarget<T>> {
  558   final List<_DragAvatar<T>> _candidateAvatars = <_DragAvatar<T>>[];
  559   final List<_DragAvatar<Object>> _rejectedAvatars = <_DragAvatar<Object>>[];
  560 
  561   bool didEnter(_DragAvatar<Object> avatar) {
  562     assert(!_candidateAvatars.contains(avatar));
  563     assert(!_rejectedAvatars.contains(avatar));
  564     if (avatar is _DragAvatar<T> && (widget.onWillAccept == null || widget.onWillAccept(avatar.data))) {
  565       setState(() {
  566         _candidateAvatars.add(avatar);
  567       });
  568       return true;
  569     } else {
  570       setState(() {
  571         _rejectedAvatars.add(avatar);
  572       });
  573       return false;
  574     }
  575   }
  576 
  577   void didLeave(_DragAvatar<Object> avatar) {
  578     assert(_candidateAvatars.contains(avatar) || _rejectedAvatars.contains(avatar));
  579     if (!mounted)
  580       return;
  581     setState(() {
  582       _candidateAvatars.remove(avatar);
  583       _rejectedAvatars.remove(avatar);
  584     });
  585     if (widget.onLeave != null)
  586       widget.onLeave(avatar.data);
  587   }
  588 
  589   void didDrop(_DragAvatar<Object> avatar) {
  590     assert(_candidateAvatars.contains(avatar));
  591     if (!mounted)
  592       return;
  593     setState(() {
  594       _candidateAvatars.remove(avatar);
  595     });
  596     if (widget.onAccept != null)
  597       widget.onAccept(avatar.data as T);
  598     if (widget.onAcceptWithDetails != null)
  599       widget.onAcceptWithDetails(DragTargetDetails<T>(data: avatar.data as T, offset: avatar._lastOffset));
  600   }
  601 
  602   void didMove(_DragAvatar<Object> avatar) {
  603     if (!mounted)
  604       return;
  605     if (widget.onMove != null)
  606       widget.onMove(DragTargetDetails<dynamic>(data: avatar.data, offset: avatar._lastOffset));
  607   }
  608 
  609   @override
  610   Widget build(BuildContext context) {
  611     assert(widget.builder != null);
  612     return MetaData(
  613       metaData: this,
  614       behavior: HitTestBehavior.translucent,
  615       child: widget.builder(context, _mapAvatarsToData<T>(_candidateAvatars), _mapAvatarsToData<Object>(_rejectedAvatars)),
  616     );
  617   }
  618 }
  619 
  620 enum _DragEndKind { dropped, canceled }
  621 typedef _OnDragEnd = void Function(Velocity velocity, Offset offset, bool wasAccepted);
  622 
  623 // The lifetime of this object is a little dubious right now. Specifically, it
  624 // lives as long as the pointer is down. Arguably it should self-immolate if the
  625 // overlay goes away. _DraggableState has some delicate logic to continue
  626 // needing this object pointer events even after it has been disposed.
  627 class _DragAvatar<T> extends Drag {
  628   _DragAvatar({
  629     @required this.overlayState,
  630     this.data,
  631     this.axis,
  632     Offset initialPosition,
  633     this.dragStartPoint = Offset.zero,
  634     this.feedback,
  635     this.feedbackOffset = Offset.zero,
  636     this.onDragEnd,
  637     @required this.ignoringFeedbackSemantics,
  638   }) : assert(overlayState != null),
  639        assert(ignoringFeedbackSemantics != null),
  640        assert(dragStartPoint != null),
  641        assert(feedbackOffset != null) {
  642     _entry = OverlayEntry(builder: _build);
  643     overlayState.insert(_entry);
  644     _position = initialPosition;
  645     updateDrag(initialPosition);
  646   }
  647 
  648   final T data;
  649   final Axis axis;
  650   final Offset dragStartPoint;
  651   final Widget feedback;
  652   final Offset feedbackOffset;
  653   final _OnDragEnd onDragEnd;
  654   final OverlayState overlayState;
  655   final bool ignoringFeedbackSemantics;
  656 
  657   _DragTargetState<T> _activeTarget;
  658   final List<_DragTargetState<T>> _enteredTargets = <_DragTargetState<T>>[];
  659   Offset _position;
  660   Offset _lastOffset;
  661   OverlayEntry _entry;
  662 
  663   @override
  664   void update(DragUpdateDetails details) {
  665     _position += _restrictAxis(details.delta);
  666     updateDrag(_position);
  667   }
  668 
  669   @override
  670   void end(DragEndDetails details) {
  671     finishDrag(_DragEndKind.dropped, _restrictVelocityAxis(details.velocity));
  672   }
  673 
  674 
  675   @override
  676   void cancel() {
  677     finishDrag(_DragEndKind.canceled);
  678   }
  679 
  680   void updateDrag(Offset globalPosition) {
  681     _lastOffset = globalPosition - dragStartPoint;
  682     _entry.markNeedsBuild();
  683     final HitTestResult result = HitTestResult();
  684     WidgetsBinding.instance.hitTest(result, globalPosition + feedbackOffset);
  685 
  686     final List<_DragTargetState<T>> targets = _getDragTargets(result.path).toList();
  687 
  688     bool listsMatch = false;
  689     if (targets.length >= _enteredTargets.length && _enteredTargets.isNotEmpty) {
  690       listsMatch = true;
  691       final Iterator<_DragTargetState<T>> iterator = targets.iterator;
  692       for (int i = 0; i < _enteredTargets.length; i += 1) {
  693         iterator.moveNext();
  694         if (iterator.current != _enteredTargets[i]) {
  695           listsMatch = false;
  696           break;
  697         }
  698       }
  699     }
  700 
  701     // If everything's the same, report moves, and bail early.
  702     if (listsMatch) {
  703       for (final _DragTargetState<T> target in _enteredTargets) {
  704         target.didMove(this);
  705       }
  706       return;
  707     }
  708 
  709     // Leave old targets.
  710     _leaveAllEntered();
  711 
  712     // Enter new targets.
  713     final _DragTargetState<T> newTarget = targets.firstWhere(
  714       (_DragTargetState<T> target) {
  715         _enteredTargets.add(target);
  716         return target.didEnter(this);
  717       },
  718       orElse: () => null,
  719     );
  720 
  721     // Report moves to the targets.
  722     for (final _DragTargetState<T> target in _enteredTargets) {
  723       target.didMove(this);
  724     }
  725 
  726     _activeTarget = newTarget;
  727   }
  728 
  729   Iterable<_DragTargetState<T>> _getDragTargets(Iterable<HitTestEntry> path) sync* {
  730     // Look for the RenderBoxes that corresponds to the hit target (the hit target
  731     // widgets build RenderMetaData boxes for us for this purpose).
  732     for (final HitTestEntry entry in path) {
  733       final HitTestTarget target = entry.target;
  734       if (target is RenderMetaData) {
  735         final dynamic metaData = target.metaData;
  736         if (metaData is _DragTargetState<T>)
  737           yield metaData;
  738       }
  739     }
  740   }
  741 
  742   void _leaveAllEntered() {
  743     for (int i = 0; i < _enteredTargets.length; i += 1)
  744       _enteredTargets[i].didLeave(this);
  745     _enteredTargets.clear();
  746   }
  747 
  748   void finishDrag(_DragEndKind endKind, [ Velocity velocity ]) {
  749     bool wasAccepted = false;
  750     if (endKind == _DragEndKind.dropped && _activeTarget != null) {
  751       _activeTarget.didDrop(this);
  752       wasAccepted = true;
  753       _enteredTargets.remove(_activeTarget);
  754     }
  755     _leaveAllEntered();
  756     _activeTarget = null;
  757     _entry.remove();
  758     _entry = null;
  759     // TODO(ianh): consider passing _entry as well so the client can perform an animation.
  760     if (onDragEnd != null)
  761       onDragEnd(velocity ?? Velocity.zero, _lastOffset, wasAccepted);
  762   }
  763 
  764   Widget _build(BuildContext context) {
  765     final RenderBox box = overlayState.context.findRenderObject() as RenderBox;
  766     final Offset overlayTopLeft = box.localToGlobal(Offset.zero);
  767     return Positioned(
  768       left: _lastOffset.dx - overlayTopLeft.dx,
  769       top: _lastOffset.dy - overlayTopLeft.dy,
  770       child: IgnorePointer(
  771         child: feedback,
  772         ignoringSemantics: ignoringFeedbackSemantics,
  773       ),
  774     );
  775   }
  776 
  777   Velocity _restrictVelocityAxis(Velocity velocity) {
  778     if (axis == null) {
  779       return velocity;
  780     }
  781     return Velocity(
  782       pixelsPerSecond: _restrictAxis(velocity.pixelsPerSecond),
  783     );
  784   }
  785 
  786   Offset _restrictAxis(Offset offset) {
  787     if (axis == null) {
  788       return offset;
  789     }
  790     if (axis == Axis.horizontal) {
  791       return Offset(offset.dx, 0.0);
  792     }
  793     return Offset(0.0, offset.dy);
  794   }
  795 }