Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
c27dc641
Commit
c27dc641
authored
Feb 11, 2016
by
Ian Hickson
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1782 from Hixie/tap-drag
Provide a Draggable that starts on drag
parents
88cefe12
8e279f32
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
507 additions
and
107 deletions
+507
-107
drag_and_drop.dart
examples/widgets/drag_and_drop.dart
+25
-10
gestures.dart
packages/flutter/lib/gestures.dart
+1
-0
multidrag.dart
packages/flutter/lib/src/gestures/multidrag.dart
+288
-0
multitap.dart
packages/flutter/lib/src/gestures/multitap.dart
+1
-1
recognizer.dart
packages/flutter/lib/src/gestures/recognizer.dart
+28
-6
velocity_tracker.dart
packages/flutter/lib/src/gestures/velocity_tracker.dart
+61
-32
drag_target.dart
packages/flutter/lib/src/widgets/drag_target.dart
+42
-58
draggable_test.dart
packages/flutter/test/widget/draggable_test.dart
+61
-0
No files found.
examples/widgets/drag_and_drop.dart
View file @
c27dc641
...
...
@@ -39,20 +39,29 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
}
}
class
Dot
extends
State
less
Component
{
Dot
({
Key
key
,
this
.
color
,
this
.
size
,
this
.
child
})
:
super
(
key:
key
);
class
Dot
extends
State
ful
Component
{
Dot
({
Key
key
,
this
.
color
,
this
.
size
,
this
.
child
,
this
.
tappable
:
false
})
:
super
(
key:
key
);
final
Color
color
;
final
double
size
;
final
Widget
child
;
final
bool
tappable
;
DotState
createState
()
=>
new
DotState
();
}
class
DotState
extends
State
<
Dot
>
{
int
taps
=
0
;
Widget
build
(
BuildContext
context
)
{
return
new
Container
(
width:
size
,
height:
size
,
decoration:
new
BoxDecoration
(
backgroundColor:
color
,
shape:
BoxShape
.
circle
),
child:
child
return
new
GestureDetector
(
onTap:
config
.
tappable
?
()
{
setState
(()
{
taps
+=
1
;
});
}
:
null
,
child:
new
Container
(
width:
config
.
size
,
height:
config
.
size
,
decoration:
new
BoxDecoration
(
backgroundColor:
config
.
color
,
border:
new
Border
.
all
(
color:
const
Color
(
0xFF000000
),
width:
taps
.
toDouble
()),
shape:
BoxShape
.
circle
),
child:
config
.
child
)
);
}
}
...
...
@@ -155,10 +164,14 @@ class DashOutlineCirclePainter extends CustomPainter {
class
MovableBall
extends
StatelessComponent
{
MovableBall
(
this
.
position
,
this
.
ballPosition
,
this
.
callback
);
final
int
position
;
final
int
ballPosition
;
final
ValueChanged
<
int
>
callback
;
static
final
GlobalKey
kBallKey
=
new
GlobalKey
();
static
const
double
kBallSize
=
50.0
;
Widget
build
(
BuildContext
context
)
{
Widget
ball
=
new
DefaultTextStyle
(
style:
Theme
.
of
(
context
).
text
.
body1
.
copyWith
(
...
...
@@ -166,8 +179,10 @@ class MovableBall extends StatelessComponent {
color:
Colors
.
white
),
child:
new
Dot
(
key:
kBallKey
,
color:
Colors
.
blue
[
700
],
size:
kBallSize
,
tappable:
true
,
child:
new
Center
(
child:
new
Text
(
'BALL'
))
)
);
...
...
packages/flutter/lib/gestures.dart
View file @
c27dc641
...
...
@@ -14,6 +14,7 @@ export 'src/gestures/events.dart';
export
'src/gestures/hit_test.dart'
;
export
'src/gestures/long_press.dart'
;
export
'src/gestures/lsq_solver.dart'
;
export
'src/gestures/multidrag.dart'
;
export
'src/gestures/multitap.dart'
;
export
'src/gestures/pointer_router.dart'
;
export
'src/gestures/recognizer.dart'
;
...
...
packages/flutter/lib/src/gestures/multidrag.dart
0 → 100644
View file @
c27dc641
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:ui'
show
Point
,
Offset
;
import
'arena.dart'
;
import
'constants.dart'
;
import
'events.dart'
;
import
'pointer_router.dart'
;
import
'recognizer.dart'
;
import
'velocity_tracker.dart'
;
typedef
Drag
GestureMultiDragStartCallback
(
Point
position
);
class
Drag
{
void
move
(
Offset
offset
)
{
}
void
end
(
Offset
velocity
)
{
}
void
cancel
()
{
}
}
abstract
class
MultiDragPointerState
{
MultiDragPointerState
(
this
.
initialPosition
);
final
Point
initialPosition
;
final
VelocityTracker
_velocityTracker
=
new
VelocityTracker
();
Drag
_client
;
Offset
get
pendingDelta
=>
_pendingDelta
;
Offset
_pendingDelta
=
Offset
.
zero
;
GestureArenaEntry
_arenaEntry
;
void
_setArenaEntry
(
GestureArenaEntry
entry
)
{
assert
(
_arenaEntry
==
null
);
assert
(
pendingDelta
!=
null
);
assert
(
_client
==
null
);
_arenaEntry
=
entry
;
}
void
resolve
(
GestureDisposition
disposition
)
{
_arenaEntry
.
resolve
(
disposition
);
}
void
_move
(
PointerMoveEvent
event
)
{
assert
(
_arenaEntry
!=
null
);
_velocityTracker
.
addPosition
(
event
.
timeStamp
,
event
.
position
);
if
(
_client
!=
null
)
{
assert
(
pendingDelta
==
null
);
_client
.
move
(
event
.
delta
);
}
else
{
assert
(
pendingDelta
!=
null
);
_pendingDelta
+=
event
.
delta
;
checkForResolutionAfterMove
();
}
return
null
;
}
/// Override this to call resolve() if the drag should be accepted or rejected.
/// This is called when a pointer movement is received, but only if the gesture
/// has not yet been resolved.
void
checkForResolutionAfterMove
()
{
}
/// Called when the gesture was accepted.
void
accepted
(
Drag
client
)
{
assert
(
_arenaEntry
!=
null
);
assert
(
_client
==
null
);
_client
=
client
;
_client
.
move
(
pendingDelta
);
_pendingDelta
=
null
;
}
/// Called when the gesture was rejected.
void
rejected
()
{
assert
(
_arenaEntry
!=
null
);
assert
(
_client
==
null
);
assert
(
pendingDelta
!=
null
);
_pendingDelta
=
null
;
_arenaEntry
=
null
;
}
void
_up
()
{
assert
(
_arenaEntry
!=
null
);
if
(
_client
!=
null
)
{
assert
(
pendingDelta
==
null
);
_client
.
end
(
_velocityTracker
.
getVelocity
());
_client
=
null
;
}
else
{
assert
(
pendingDelta
!=
null
);
_pendingDelta
=
null
;
}
_arenaEntry
=
null
;
}
void
_cancel
()
{
assert
(
_arenaEntry
!=
null
);
if
(
_client
!=
null
)
{
assert
(
pendingDelta
==
null
);
_client
.
cancel
();
_client
=
null
;
}
else
{
assert
(
pendingDelta
!=
null
);
_pendingDelta
=
null
;
}
_arenaEntry
=
null
;
}
void
dispose
()
{
}
}
abstract
class
MultiDragGestureRecognizer
<
T
extends
MultiDragPointerState
>
extends
GestureRecognizer
{
MultiDragGestureRecognizer
({
PointerRouter
pointerRouter
,
GestureArena
gestureArena
,
this
.
onStart
})
:
_pointerRouter
=
pointerRouter
,
_gestureArena
=
gestureArena
{
assert
(
pointerRouter
!=
null
);
assert
(
gestureArena
!=
null
);
}
final
PointerRouter
_pointerRouter
;
final
GestureArena
_gestureArena
;
GestureMultiDragStartCallback
onStart
;
Map
<
int
,
T
>
_pointers
=
<
int
,
T
>{};
void
addPointer
(
PointerDownEvent
event
)
{
assert
(
_pointers
!=
null
);
assert
(
event
.
pointer
!=
null
);
assert
(
event
.
position
!=
null
);
assert
(!
_pointers
.
containsKey
(
event
.
pointer
));
T
state
=
createNewPointerState
(
event
);
_pointers
[
event
.
pointer
]
=
state
;
_pointerRouter
.
addRoute
(
event
.
pointer
,
handleEvent
);
state
.
_setArenaEntry
(
_gestureArena
.
add
(
event
.
pointer
,
this
));
}
T
createNewPointerState
(
PointerDownEvent
event
);
void
handleEvent
(
PointerEvent
event
)
{
assert
(
_pointers
!=
null
);
assert
(
event
.
pointer
!=
null
);
assert
(
event
.
timeStamp
!=
null
);
assert
(
event
.
position
!=
null
);
assert
(
_pointers
.
containsKey
(
event
.
pointer
));
T
state
=
_pointers
[
event
.
pointer
];
if
(
event
is
PointerMoveEvent
)
{
state
.
_move
(
event
);
}
else
if
(
event
is
PointerUpEvent
)
{
assert
(
event
.
delta
==
Offset
.
zero
);
state
.
_up
();
_removeState
(
event
.
pointer
);
}
else
if
(
event
is
PointerCancelEvent
)
{
assert
(
event
.
delta
==
Offset
.
zero
);
state
.
_cancel
();
_removeState
(
event
.
pointer
);
}
else
if
(
event
is
!
PointerDownEvent
)
{
// we get the PointerDownEvent that resulted in our addPointer gettig called since we
// add ourselves to the pointer router then (before the pointer router has heard of
// the event).
assert
(
false
);
}
}
void
acceptGesture
(
int
pointer
)
{
assert
(
_pointers
!=
null
);
T
state
=
_pointers
[
pointer
];
assert
(
state
!=
null
);
Drag
drag
;
if
(
onStart
!=
null
)
drag
=
onStart
(
state
.
initialPosition
);
if
(
drag
!=
null
)
{
state
.
accepted
(
drag
);
}
else
{
_removeState
(
pointer
);
}
}
void
rejectGesture
(
int
pointer
)
{
assert
(
_pointers
!=
null
);
if
(
_pointers
.
containsKey
(
pointer
))
{
T
state
=
_pointers
[
pointer
];
assert
(
state
!=
null
);
state
.
rejected
();
_removeState
(
pointer
);
}
// else we already preemptively forgot about it (e.g. we got an up event)
}
void
_removeState
(
int
pointer
)
{
assert
(
_pointers
!=
null
);
assert
(
_pointers
.
containsKey
(
pointer
));
_pointerRouter
.
removeRoute
(
pointer
,
handleEvent
);
_pointers
[
pointer
].
dispose
();
_pointers
.
remove
(
pointer
);
}
void
dispose
()
{
for
(
int
pointer
in
_pointers
.
keys
)
_removeState
(
pointer
);
_pointers
=
null
;
super
.
dispose
();
}
}
class
_ImmediatePointerState
extends
MultiDragPointerState
{
_ImmediatePointerState
(
Point
initialPosition
)
:
super
(
initialPosition
);
void
checkForResolutionAfterMove
()
{
assert
(
pendingDelta
!=
null
);
if
(
pendingDelta
.
distance
>
kTouchSlop
)
resolve
(
GestureDisposition
.
accepted
);
}
}
class
ImmediateMultiDragGestureRecognizer
extends
MultiDragGestureRecognizer
<
_ImmediatePointerState
>
{
ImmediateMultiDragGestureRecognizer
({
PointerRouter
pointerRouter
,
GestureArena
gestureArena
,
GestureMultiDragStartCallback
onStart
})
:
super
(
pointerRouter:
pointerRouter
,
gestureArena:
gestureArena
,
onStart:
onStart
);
_ImmediatePointerState
createNewPointerState
(
PointerDownEvent
event
)
{
return
new
_ImmediatePointerState
(
event
.
position
);
}
}
class
_DelayedPointerState
extends
MultiDragPointerState
{
_DelayedPointerState
(
Point
initialPosition
,
Duration
delay
)
:
super
(
initialPosition
)
{
assert
(
delay
!=
null
);
_timer
=
new
Timer
(
delay
,
_delayPassed
);
}
Timer
_timer
;
void
_delayPassed
()
{
assert
(
_timer
!=
null
);
assert
(
pendingDelta
!=
null
);
assert
(
pendingDelta
.
distance
<=
kTouchSlop
);
resolve
(
GestureDisposition
.
accepted
);
_timer
=
null
;
}
void
accepted
(
Drag
client
)
{
_timer
?.
cancel
();
_timer
=
null
;
super
.
accepted
(
client
);
}
void
checkForResolutionAfterMove
()
{
assert
(
_timer
!=
null
);
assert
(
pendingDelta
!=
null
);
if
(
pendingDelta
.
distance
>
kTouchSlop
)
resolve
(
GestureDisposition
.
rejected
);
}
void
dispose
()
{
_timer
?.
cancel
();
_timer
=
null
;
super
.
dispose
();
}
}
class
DelayedMultiDragGestureRecognizer
extends
MultiDragGestureRecognizer
<
_DelayedPointerState
>
{
DelayedMultiDragGestureRecognizer
({
PointerRouter
pointerRouter
,
GestureArena
gestureArena
,
GestureMultiDragStartCallback
onStart
,
Duration
delay:
kLongPressTimeout
})
:
_delay
=
delay
,
super
(
pointerRouter:
pointerRouter
,
gestureArena:
gestureArena
,
onStart:
onStart
)
{
assert
(
delay
!=
null
);
}
Duration
get
delay
=>
_delay
;
Duration
_delay
;
void
set
delay
(
Duration
value
)
{
assert
(
value
!=
null
);
_delay
=
value
;
}
_DelayedPointerState
createNewPointerState
(
PointerDownEvent
event
)
{
return
new
_DelayedPointerState
(
event
.
position
,
_delay
);
}
}
packages/flutter/lib/src/gestures/multitap.dart
View file @
c27dc641
...
...
@@ -290,7 +290,7 @@ class _TapGesture extends _TapTracker {
if
(
_wonArena
)
reject
();
else
entry
.
resolve
(
GestureDisposition
.
rejected
);
entry
.
resolve
(
GestureDisposition
.
rejected
);
// eventually calls reject()
}
void
_check
()
{
...
...
packages/flutter/lib/src/gestures/recognizer.dart
View file @
c27dc641
...
...
@@ -13,23 +13,45 @@ import 'pointer_router.dart';
export
'pointer_router.dart'
show
PointerRouter
;
/// The base class that all GestureRecognizers should inherit from.
///
/// Provides a basic API that can be used by classes that work with
/// gesture recognizers but don't care about the specific details of
/// the gestures recognizers themselves.
abstract
class
GestureRecognizer
extends
GestureArenaMember
{
///
Calls this with the pointerdown event of each pointer that should b
e
///
considered for this gesture
.
///
Registers a new pointer that might be relevant to this gestur
e
///
detector
.
///
/// It's the GestureRecognizer's responsibility to then add itself to the
/// global pointer router to receive subsequent events for this pointer.
/// The owner of this gesture recognizer calls addPointer() with the
/// PointerDownEvent of each pointer that should be considered for
/// this gesture.
///
/// It's the GestureRecognizer's responsibility to then add itself
/// to the global pointer router (see [PointerRouter]) to receive
/// subsequent events for this pointer, and to add the pointer to
/// the global gesture arena manager (see [GestureArena]) to track
/// that pointer.
void
addPointer
(
PointerDownEvent
event
);
/// Releases any resources used by the object.
///
/// This method is called when the object is no longer needed (e.g. a gesture
/// recogniser is being unregistered from a [GestureDetector]).
/// This method is called by the owner of this gesture recognizer
/// when the object is no longer needed (e.g. when a gesture
/// recogniser is being unregistered from a [GestureDetector], the
/// GestureDetector widget calls this method).
void
dispose
()
{
}
}
/// Base class for gesture recognizers that can only recognize one
/// gesture at a time. For example, a single [TapGestureRecognizer]
/// can never recognize two taps happening simultaneously, even if
/// multiple pointers are placed on the same widget.
///
/// This is in contrast to, for instance, [MultiTapGestureRecognizer],
/// which manages each pointer independently and can consider multiple
/// simultaneous touches to each result in a separate tap.
abstract
class
OneSequenceGestureRecognizer
extends
GestureRecognizer
{
OneSequenceGestureRecognizer
({
PointerRouter
router
,
...
...
packages/flutter/lib/src/gestures/velocity_tracker.dart
View file @
c27dc641
...
...
@@ -8,25 +8,27 @@ import 'lsq_solver.dart';
export
'dart:ui'
show
Point
,
Offset
;
class
_Estimator
{
int
degree
;
Duration
time
;
List
<
double
>
xCoefficients
;
List
<
double
>
yCoefficients
;
double
confidence
;
class
_Estimate
{
const
_Estimate
({
this
.
xCoefficients
,
this
.
yCoefficients
,
this
.
time
,
this
.
degree
,
this
.
confidence
});
final
List
<
double
>
xCoefficients
;
final
List
<
double
>
yCoefficients
;
final
Duration
time
;
final
int
degree
;
final
double
confidence
;
String
toString
()
{
return
'Estimat
or(degree:
$degree
, '
'time:
$time
, '
'confidence:
$confidenc
e
, '
'xCoefficients:
$xCoefficients
, '
'yCoefficients:
$yCoefficients
)'
;
return
'Estimat
e(xCoefficients:
$xCoefficients
, '
'yCoefficients:
$yCoefficients
, '
'time:
$tim
e
, '
'degree:
$degree
, '
'confidence:
$confidence
)'
;
}
}
abstract
class
_VelocityTrackerStrategy
{
void
addMovement
(
Duration
timeStamp
,
Point
position
);
bool
getEstimator
(
_Estimator
estimator
);
_Estimate
getEstimate
(
);
void
clear
();
}
...
...
@@ -80,7 +82,7 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
movement
.
position
=
position
;
}
bool
getEstimator
(
_Estimator
estimator
)
{
_Estimate
getEstimate
(
)
{
// Iterate over movement samples in reverse time order and collect samples.
List
<
double
>
x
=
new
List
<
double
>();
List
<
double
>
y
=
new
List
<
double
>();
...
...
@@ -107,7 +109,7 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
}
while
(
m
<
kHistorySize
);
if
(
m
==
0
)
// because we broke out of the loop above after age > kHorizonMilliseconds
return
false
;
// no data
return
null
;
// no data
// Calculate a least squares polynomial fit.
int
n
=
degree
;
...
...
@@ -121,24 +123,26 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
LeastSquaresSolver
ySolver
=
new
LeastSquaresSolver
(
time
,
y
,
w
);
PolynomialFit
yFit
=
ySolver
.
solve
(
n
);
if
(
yFit
!=
null
)
{
estimator
.
xCoefficients
=
xFit
.
coefficients
;
estimator
.
yCoefficients
=
yFit
.
coefficients
;
estimator
.
time
=
newestMovement
.
eventTime
;
estimator
.
degree
=
n
;
estimator
.
confidence
=
xFit
.
confidence
*
yFit
.
confidence
;
return
true
;
return
new
_Estimate
(
xCoefficients:
xFit
.
coefficients
,
yCoefficients:
yFit
.
coefficients
,
time:
newestMovement
.
eventTime
,
degree:
n
,
confidence:
xFit
.
confidence
*
yFit
.
confidence
);
}
}
}
// No velocity data available for this pointer, but we do have its current
// position.
estimator
.
xCoefficients
=
<
double
>[
x
[
0
]
];
estimator
.
yCoefficients
=
<
double
>[
y
[
0
]
];
estimator
.
time
=
newestMovement
.
eventTime
;
estimator
.
degree
=
0
;
estimator
.
confidence
=
1.0
;
return
true
;
return
new
_Estimate
(
xCoefficients:
<
double
>[
x
[
0
]
],
yCoefficients:
<
double
>[
y
[
0
]
],
time:
newestMovement
.
eventTime
,
degree:
0
,
confidence:
1.0
);
}
void
clear
()
{
...
...
@@ -204,27 +208,52 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
}
/// Computes a pointer velocity based on data from PointerMove events.
///
/// The input data is provided by calling addPosition(). Adding data
/// is cheap.
///
/// To obtain a velocity, call getVelocity(). This will compute the
/// velocity based on the data added so far. Only call this when you
/// need to use the velocity, as it is comparatively expensive.
///
/// The quality of the velocity estimation will be better if more data
/// points have been received.
class
VelocityTracker
{
static
const
int
kAssumePointerMoveStoppedTimeMs
=
40
;
/// The maximum length of time between two move events to allow
/// before assuming the pointer stopped.
static
const
Duration
kAssumePointerMoveStoppedTime
=
const
Duration
(
milliseconds:
40
);
VelocityTracker
()
:
_strategy
=
_createStrategy
();
Duration
_lastTimeStamp
=
const
Duration
();
_VelocityTrackerStrategy
_strategy
;
/// Add a given position corresponding to a specific time.
///
/// If [kAssumePointerMoveStoppedTime] has elapsed since the last
/// call, then earlier data will be discarded.
void
addPosition
(
Duration
timeStamp
,
Point
position
)
{
if
(
(
timeStamp
-
_lastTimeStamp
).
inMilliseconds
>=
kAssumePointerMoveStoppedTimeMs
)
if
(
timeStamp
-
_lastTimeStamp
>=
kAssumePointerMoveStoppedTime
)
_strategy
.
clear
();
_lastTimeStamp
=
timeStamp
;
_strategy
.
addMovement
(
timeStamp
,
position
);
}
/// Computes the velocity of the pointer at the time of the last
/// provided data point.
///
/// This can be expensive. Only call this when you need the velocity.
///
/// getVelocity() will return null if no estimate is available or if
/// the velocity is zero.
Offset
getVelocity
()
{
_Estimat
or
estimator
=
new
_Estimator
();
if
(
_strategy
.
getEstimator
(
estimator
)
&&
estimator
.
degree
>=
1
)
{
_Estimat
e
estimate
=
_strategy
.
getEstimate
();
if
(
estimate
!=
null
&&
estimate
.
degree
>=
1
)
{
return
new
Offset
(
// convert from pixels/ms to pixels/s
estimat
or
.
xCoefficients
[
1
]
*
1000
,
estimat
or
.
yCoefficients
[
1
]
*
1000
estimat
e
.
xCoefficients
[
1
]
*
1000
,
estimat
e
.
yCoefficients
[
1
]
*
1000
);
}
return
null
;
...
...
packages/flutter/lib/src/widgets/drag_target.dart
View file @
c27dc641
...
...
@@ -16,7 +16,6 @@ import 'overlay.dart';
typedef
bool
DragTargetWillAccept
<
T
>(
T
data
);
typedef
void
DragTargetAccept
<
T
>(
T
data
);
typedef
Widget
DragTargetBuilder
<
T
>(
BuildContext
context
,
List
<
T
>
candidateData
,
List
<
dynamic
>
rejectedData
);
typedef
void
DragStartCallback
(
Point
position
,
int
pointer
);
/// Where the [Draggable] should be anchored during a drag.
enum
DragAnchor
{
...
...
@@ -81,11 +80,9 @@ abstract class DraggableBase<T> extends StatefulComponent {
/// dragged at a time.
final
int
maxSimultaneousDrags
;
/// Should return a GestureRecognizer instance that is configured to call the starter
/// argument when the drag is to begin. The arena for the pointer must not yet have
/// resolved at the time that the callback is invoked, because the draggable itself
/// is going to attempt to win the pointer's arena in that case.
GestureRecognizer
createRecognizer
(
PointerRouter
router
,
DragStartCallback
starter
);
/// Should return a new MultiDragGestureRecognizer instance
/// constructed with the given arguments.
MultiDragGestureRecognizer
createRecognizer
(
PointerRouter
router
,
GestureArena
arena
,
GestureMultiDragStartCallback
starter
);
_DraggableState
<
T
>
createState
()
=>
new
_DraggableState
<
T
>();
}
...
...
@@ -112,11 +109,11 @@ class Draggable<T> extends DraggableBase<T> {
maxSimultaneousDrags:
maxSimultaneousDrags
);
GestureRecognizer
createRecognizer
(
PointerRouter
router
,
DragStartCallback
starter
)
{
return
new
MultiTap
GestureRecognizer
(
r
outer:
router
,
gestureArena:
Gesturer
.
instance
.
gestureA
rena
,
on
TapDown
:
starter
MultiDragGestureRecognizer
createRecognizer
(
PointerRouter
router
,
GestureArena
arena
,
GestureMulti
DragStartCallback
starter
)
{
return
new
ImmediateMultiDrag
GestureRecognizer
(
pointerR
outer:
router
,
gestureArena:
a
rena
,
on
Start
:
starter
);
}
}
...
...
@@ -143,50 +140,44 @@ class LongPressDraggable<T> extends DraggableBase<T> {
maxSimultaneousDrags:
maxSimultaneousDrags
);
GestureRecognizer
createRecognizer
(
PointerRouter
router
,
DragStartCallback
starter
)
{
return
new
MultiTapGestureRecognizer
(
router:
router
,
gestureArena:
Gesturer
.
instance
.
gestureArena
,
longTapDelay:
kLongPressTimeout
,
onLongTapDown:
(
Point
position
,
int
pointer
)
{
userFeedback
.
performHapticFeedback
(
HapticFeedbackType
.
virtualKey
);
starter
(
position
,
pointer
);
MultiDragGestureRecognizer
createRecognizer
(
PointerRouter
router
,
GestureArena
arena
,
GestureMultiDragStartCallback
starter
)
{
return
new
DelayedMultiDragGestureRecognizer
(
pointerRouter:
router
,
gestureArena:
arena
,
delay:
kLongPressTimeout
,
onStart:
(
Point
position
)
{
Drag
result
=
starter
(
position
);
if
(
result
!=
null
)
userFeedback
.
performHapticFeedback
(
HapticFeedbackType
.
virtualKey
);
return
result
;
}
);
}
}
class
_DraggableState
<
T
>
extends
State
<
DraggableBase
<
T
>>
implements
GestureArenaMember
{
PointerRouter
get
router
=>
Gesturer
.
instance
.
pointerRouter
;
class
_DraggableState
<
T
>
extends
State
<
DraggableBase
<
T
>>
{
void
initState
()
{
super
.
initState
();
_recognizer
=
config
.
createRecognizer
(
router
,
_startDrag
);
_recognizer
=
config
.
createRecognizer
(
Gesturer
.
instance
.
pointerRouter
,
Gesturer
.
instance
.
gestureArena
,
_startDrag
);
}
GestureRecognizer
_recognizer
;
Map
<
int
,
GestureArenaEntry
>
_activePointers
=
<
int
,
GestureArenaEntry
>{};
int
_activeCount
=
0
;
void
_routePointer
(
PointerEvent
event
)
{
_activePointers
[
event
.
pointer
]
=
Gesturer
.
instance
.
gestureArena
.
add
(
event
.
pointer
,
this
);
if
(
config
.
maxSimultaneousDrags
!=
null
&&
_activeCount
>=
config
.
maxSimultaneousDrags
)
return
;
_recognizer
.
addPointer
(
event
);
}
void
acceptGesture
(
int
pointer
)
{
_activePointers
.
remove
(
pointer
);
}
void
rejectGesture
(
int
pointer
)
{
_activePointers
.
remove
(
pointer
);
}
void
_startDrag
(
Point
position
,
int
pointer
)
{
_DragAvatar
_startDrag
(
Point
position
)
{
if
(
config
.
maxSimultaneousDrags
!=
null
&&
_activeCount
>=
config
.
maxSimultaneousDrags
)
return
;
assert
(
_activePointers
.
containsKey
(
pointer
));
_activePointers
[
pointer
].
resolve
(
GestureDisposition
.
accepted
);
return
null
;
Point
dragStartPoint
;
switch
(
config
.
dragAnchor
)
{
case
DragAnchor
.
child
:
...
...
@@ -200,9 +191,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
setState
(()
{
_activeCount
+=
1
;
});
new
_DragAvatar
<
T
>(
pointer:
pointer
,
router:
router
,
return
new
_DragAvatar
<
T
>(
overlay:
Overlay
.
of
(
context
),
data:
config
.
data
,
initialPosition:
position
,
...
...
@@ -305,10 +294,8 @@ enum _DragEndKind { dropped, canceled }
// overlay goes away, or maybe even if the Draggable that created goes away.
// This will probably need to be changed once we have more experience with using
// this widget.
class
_DragAvatar
<
T
>
{
class
_DragAvatar
<
T
>
extends
Drag
{
_DragAvatar
({
this
.
pointer
,
this
.
router
,
OverlayState
overlay
,
this
.
data
,
Point
initialPosition
,
...
...
@@ -317,19 +304,15 @@ class _DragAvatar<T> {
this
.
feedbackOffset
:
Offset
.
zero
,
this
.
onDragEnd
})
{
assert
(
pointer
!=
null
);
assert
(
router
!=
null
);
assert
(
overlay
!=
null
);
assert
(
dragStartPoint
!=
null
);
assert
(
feedbackOffset
!=
null
);
router
.
addRoute
(
pointer
,
handleEvent
);
_entry
=
new
OverlayEntry
(
builder:
_build
);
overlay
.
insert
(
_entry
);
_position
=
initialPosition
;
update
(
initialPosition
);
}
final
int
pointer
;
final
PointerRouter
router
;
final
T
data
;
final
Point
dragStartPoint
;
final
Widget
feedback
;
...
...
@@ -338,18 +321,20 @@ class _DragAvatar<T> {
_DragTargetState
_activeTarget
;
bool
_activeTargetWillAcceptDrop
=
false
;
Point
_position
;
Offset
_lastOffset
;
OverlayEntry
_entry
;
void
handleEvent
(
PointerEvent
event
)
{
if
(
event
is
PointerUpEvent
)
{
update
(
event
.
position
);
finish
(
_DragEndKind
.
dropped
);
}
else
if
(
event
is
PointerCancelEvent
)
{
finish
(
_DragEndKind
.
canceled
);
}
else
if
(
event
is
PointerMoveEvent
)
{
update
(
event
.
position
);
}
// Drag API
void
move
(
Offset
offset
)
{
_position
+=
offset
;
update
(
_position
);
}
void
end
(
Offset
velocity
)
{
finish
(
_DragEndKind
.
dropped
);
}
void
cancel
()
{
finish
(
_DragEndKind
.
canceled
);
}
void
update
(
Point
globalPosition
)
{
...
...
@@ -390,7 +375,6 @@ class _DragAvatar<T> {
_activeTargetWillAcceptDrop
=
false
;
_entry
.
remove
();
_entry
=
null
;
router
.
removeRoute
(
pointer
,
handleEvent
);
if
(
onDragEnd
!=
null
)
onDragEnd
();
}
...
...
packages/flutter/test/widget/draggable_test.dart
View file @
c27dc641
...
...
@@ -168,7 +168,68 @@ void main() {
tester
.
pump
();
expect
(
events
,
equals
(<
String
>[
'tap'
,
'tap'
,
'drop'
]));
events
.
clear
();
});
testWidgets
((
WidgetTester
tester
)
{
TestPointer
pointer
=
new
TestPointer
(
7
);
List
<
String
>
events
=
<
String
>[];
Point
firstLocation
,
secondLocation
;
tester
.
pumpWidget
(
new
MaterialApp
(
routes:
<
String
,
RouteBuilder
>{
'/'
:
(
RouteArguments
args
)
{
return
new
Column
(
children:
<
Widget
>[
new
Draggable
(
data:
1
,
child:
new
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
()
{
events
.
add
(
'tap'
);
},
child:
new
Container
(
child:
new
Text
(
'Button'
)
)
),
feedback:
new
Text
(
'Dragging'
)
),
new
DragTarget
(
builder:
(
context
,
data
,
rejects
)
{
return
new
Text
(
'Target'
);
},
onAccept:
(
data
)
{
events
.
add
(
'drop'
);
}
),
]);
},
}
));
expect
(
events
,
isEmpty
);
expect
(
tester
.
findText
(
'Button'
),
isNotNull
);
expect
(
tester
.
findText
(
'Target'
),
isNotNull
);
expect
(
events
,
isEmpty
);
tester
.
tap
(
tester
.
findText
(
'Button'
));
expect
(
events
,
equals
(<
String
>[
'tap'
]));
events
.
clear
();
firstLocation
=
tester
.
getCenter
(
tester
.
findText
(
'Button'
));
tester
.
dispatchEvent
(
pointer
.
down
(
firstLocation
),
firstLocation
);
tester
.
pump
();
secondLocation
=
tester
.
getCenter
(
tester
.
findText
(
'Target'
));
tester
.
dispatchEvent
(
pointer
.
move
(
secondLocation
),
firstLocation
);
tester
.
pump
();
expect
(
events
,
isEmpty
);
tester
.
dispatchEvent
(
pointer
.
up
(),
firstLocation
);
tester
.
pump
();
expect
(
events
,
equals
(<
String
>[
'drop'
]));
events
.
clear
();
});
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment