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
cea055ef
Unverified
Commit
cea055ef
authored
Aug 12, 2020
by
LongCatIsLooong
Committed by
GitHub
Aug 12, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make Scrollable's free scroll initial velocity matches that of iOS (#60501)
parent
54f21ec7
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
311 additions
and
32 deletions
+311
-32
README.md
dev/benchmarks/microbenchmarks/README.md
+1
-1
velocity_tracker_bench.dart
.../microbenchmarks/lib/gestures/velocity_tracker_bench.dart
+22
-17
monodrag.dart
packages/flutter/lib/src/gestures/monodrag.dart
+29
-1
velocity_tracker.dart
packages/flutter/lib/src/gestures/velocity_tracker.dart
+109
-2
scroll_configuration.dart
packages/flutter/lib/src/widgets/scroll_configuration.dart
+32
-0
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+2
-0
velocity_tracker_test.dart
packages/flutter/test/gestures/velocity_tracker_test.dart
+67
-0
scroll_behavior_test.dart
packages/flutter/test/widgets/scroll_behavior_test.dart
+12
-0
scrollable_test.dart
packages/flutter/test/widgets/scrollable_test.dart
+2
-3
controller.dart
packages/flutter_test/lib/src/controller.dart
+8
-8
controller_test.dart
packages/flutter_test/test/controller_test.dart
+27
-0
No files found.
dev/benchmarks/microbenchmarks/README.md
View file @
cea055ef
...
@@ -5,7 +5,7 @@ window to see the device logs, then, in a different window, run any of
...
@@ -5,7 +5,7 @@ window to see the device logs, then, in a different window, run any of
these:
these:
```
```
flutter run --release lib/gestures/velocity_tracker_
data
.dart
flutter run --release lib/gestures/velocity_tracker_
bench
.dart
flutter run --release lib/gestures/gesture_detector_bench.dart
flutter run --release lib/gestures/gesture_detector_bench.dart
flutter run --release lib/stocks/animation_bench.dart
flutter run --release lib/stocks/animation_bench.dart
flutter run --release lib/stocks/build_bench.dart
flutter run --release lib/stocks/build_bench.dart
...
...
dev/benchmarks/microbenchmarks/lib/gestures/velocity_tracker_bench.dart
View file @
cea055ef
...
@@ -11,26 +11,31 @@ const int _kNumIters = 10000;
...
@@ -11,26 +11,31 @@ const int _kNumIters = 10000;
void
main
(
)
{
void
main
(
)
{
assert
(
false
,
"Don't run benchmarks in checked mode! Use 'flutter run --release'."
);
assert
(
false
,
"Don't run benchmarks in checked mode! Use 'flutter run --release'."
);
final
VelocityTracker
tracker
=
VelocityTracker
();
final
BenchmarkResultPrinter
printer
=
BenchmarkResultPrinter
();
final
List
<
VelocityTracker
>
trackers
=
<
VelocityTracker
>[
VelocityTracker
(),
IOSScrollViewFlingVelocityTracker
()];
final
Stopwatch
watch
=
Stopwatch
();
final
Stopwatch
watch
=
Stopwatch
();
print
(
'Velocity tracker benchmark...'
);
watch
.
start
();
for
(
final
VelocityTracker
tracker
in
trackers
)
{
for
(
int
i
=
0
;
i
<
_kNumIters
;
i
+=
1
)
{
final
String
trackerType
=
tracker
.
runtimeType
.
toString
();
for
(
final
PointerEvent
event
in
velocityEventData
)
{
print
(
'
$trackerType
benchmark...'
);
if
(
event
is
PointerDownEvent
||
event
is
PointerMoveEvent
)
watch
.
reset
();
tracker
.
addPosition
(
event
.
timeStamp
,
event
.
position
);
watch
.
start
();
if
(
event
is
PointerUpEvent
)
for
(
int
i
=
0
;
i
<
_kNumIters
;
i
+=
1
)
{
tracker
.
getVelocity
();
for
(
final
PointerEvent
event
in
velocityEventData
)
{
if
(
event
is
PointerDownEvent
||
event
is
PointerMoveEvent
)
tracker
.
addPosition
(
event
.
timeStamp
,
event
.
position
);
if
(
event
is
PointerUpEvent
)
tracker
.
getVelocity
();
}
}
}
watch
.
stop
();
printer
.
addResult
(
description:
'Velocity tracker:
$trackerType
'
,
value:
watch
.
elapsedMicroseconds
/
_kNumIters
,
unit:
'µs per iteration'
,
name:
'velocity_tracker_iteration_
$trackerType
'
,
);
}
}
watch
.
stop
();
final
BenchmarkResultPrinter
printer
=
BenchmarkResultPrinter
();
printer
.
addResult
(
description:
'Velocity tracker'
,
value:
watch
.
elapsedMicroseconds
/
_kNumIters
,
unit:
'µs per iteration'
,
name:
'velocity_tracker_iteration'
,
);
printer
.
printToStdout
();
printer
.
printToStdout
();
}
}
packages/flutter/lib/src/gestures/monodrag.dart
View file @
cea055ef
...
@@ -34,6 +34,8 @@ typedef GestureDragEndCallback = void Function(DragEndDetails details);
...
@@ -34,6 +34,8 @@ typedef GestureDragEndCallback = void Function(DragEndDetails details);
/// See [DragGestureRecognizer.onCancel].
/// See [DragGestureRecognizer.onCancel].
typedef
GestureDragCancelCallback
=
void
Function
();
typedef
GestureDragCancelCallback
=
void
Function
();
typedef
GestureVelocityTrackerBuilder
=
VelocityTracker
Function
(
PointerEvent
event
);
/// Recognizes movement.
/// Recognizes movement.
///
///
/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer]
/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer]
...
@@ -64,9 +66,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
...
@@ -64,9 +66,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
Object
?
debugOwner
,
Object
?
debugOwner
,
PointerDeviceKind
?
kind
,
PointerDeviceKind
?
kind
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
velocityTrackerBuilder
=
_defaultBuilder
,
})
:
assert
(
dragStartBehavior
!=
null
),
})
:
assert
(
dragStartBehavior
!=
null
),
super
(
debugOwner:
debugOwner
,
kind:
kind
);
super
(
debugOwner:
debugOwner
,
kind:
kind
);
static
VelocityTracker
_defaultBuilder
(
PointerEvent
ev
)
=>
VelocityTracker
();
/// Configure the behavior of offsets sent to [onStart].
/// Configure the behavior of offsets sent to [onStart].
///
///
/// If set to [DragStartBehavior.start], the [onStart] callback will be called
/// If set to [DragStartBehavior.start], the [onStart] callback will be called
...
@@ -170,6 +174,30 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
...
@@ -170,6 +174,30 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// If null then [kMaxFlingVelocity] is used.
/// If null then [kMaxFlingVelocity] is used.
double
?
maxFlingVelocity
;
double
?
maxFlingVelocity
;
/// Determines the type of velocity estimation method to use for a potential
/// drag gesture, when a new pointer is added.
///
/// To estimate the velocity of a gesture, [DragGestureRecognizer] calls
/// [velocityTrackerBuilder] when it starts to track a new pointer in
/// [addAllowedPointer], and add subsequent updates on the pointer to the
/// resulting velocity tracker, until the gesture recognizer stops tracking
/// the pointer. This allows you to specify a different velocity estimation
/// strategy for each allowed pointer added, by changing the type of velocity
/// tracker this [GestureVelocityTrackerBuilder] returns.
///
/// If left unspecified the default [velocityTrackerBuilder] creates a new
/// [VelocityTracker] for every pointer added.
///
/// See also:
///
/// * [VelocityTracker], a velocity tracker that uses least squares estimation
/// on the 20 most recent pointer data samples. It's a well-rounded velocity
/// tracker and is used by default.
/// * [IOSScrollViewFlingVelocityTracker], a specialized velocity tracker for
/// determining the initial fling velocity for a [Scrollable] on iOS, to
/// match the native behavior on that platform.
GestureVelocityTrackerBuilder
velocityTrackerBuilder
;
_DragState
_state
=
_DragState
.
ready
;
_DragState
_state
=
_DragState
.
ready
;
late
OffsetPair
_initialPosition
;
late
OffsetPair
_initialPosition
;
late
OffsetPair
_pendingDragOffset
;
late
OffsetPair
_pendingDragOffset
;
...
@@ -225,7 +253,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
...
@@ -225,7 +253,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
@override
@override
void
addAllowedPointer
(
PointerEvent
event
)
{
void
addAllowedPointer
(
PointerEvent
event
)
{
startTrackingPointer
(
event
.
pointer
,
event
.
transform
);
startTrackingPointer
(
event
.
pointer
,
event
.
transform
);
_velocityTrackers
[
event
.
pointer
]
=
VelocityTracker
(
);
_velocityTrackers
[
event
.
pointer
]
=
velocityTrackerBuilder
(
event
);
if
(
_state
==
_DragState
.
ready
)
{
if
(
_state
==
_DragState
.
ready
)
{
_state
=
_DragState
.
possible
;
_state
=
_DragState
.
possible
;
_initialPosition
=
OffsetPair
(
global:
event
.
position
,
local:
event
.
localPosition
);
_initialPosition
=
OffsetPair
(
global:
event
.
position
,
local:
event
.
localPosition
);
...
...
packages/flutter/lib/src/gestures/velocity_tracker.dart
View file @
cea055ef
...
@@ -192,8 +192,8 @@ class VelocityTracker {
...
@@ -192,8 +192,8 @@ class VelocityTracker {
if
(
sample
==
null
)
if
(
sample
==
null
)
break
;
break
;
final
double
age
=
(
newestSample
.
time
-
sample
.
time
).
inMi
lliseconds
.
toDouble
()
;
final
double
age
=
(
newestSample
.
time
-
sample
.
time
).
inMi
croseconds
.
toDouble
()
/
1000
;
final
double
delta
=
(
sample
.
time
-
previousSample
.
time
).
inMi
lliseconds
.
abs
().
toDouble
()
;
final
double
delta
=
(
sample
.
time
-
previousSample
.
time
).
inMi
croseconds
.
abs
().
toDouble
()
/
1000
;
previousSample
=
sample
;
previousSample
=
sample
;
if
(
age
>
_horizonMilliseconds
||
delta
>
_assumePointerMoveStoppedMilliseconds
)
if
(
age
>
_horizonMilliseconds
||
delta
>
_assumePointerMoveStoppedMilliseconds
)
break
;
break
;
...
@@ -250,3 +250,110 @@ class VelocityTracker {
...
@@ -250,3 +250,110 @@ class VelocityTracker {
return
Velocity
(
pixelsPerSecond:
estimate
.
pixelsPerSecond
);
return
Velocity
(
pixelsPerSecond:
estimate
.
pixelsPerSecond
);
}
}
}
}
/// A [VelocityTracker] subclass that provides a close approximation of iOS
/// scroll view's velocity estimation strategy.
///
/// The estimated velocity reported by this class is a close approximation of
/// the velocity an iOS scroll view would report with the same
/// [PointerMoveEvent]s, when the touch that initiates a fling is released.
///
/// This class differs from the [VelocityTracker] class in that it uses weighted
/// average of the latest few velocity samples of the tracked pointer, instead
/// of doing a linear regression on a relatively large amount of data points, to
/// estimate the velocity of the tracked pointer. Adding data points and
/// estimating the velocity are both cheap.
///
/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. The
/// esimated velocity is typically used as the initial flinging velocity of a
/// `Scrollable`, when its drag gesture ends.
///
/// See also:
///
/// * [scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619385-scrollviewwillenddragging),
/// the iOS method that reports the fling velocity when the touch is released.
class
IOSScrollViewFlingVelocityTracker
extends
VelocityTracker
{
/// The velocity estimation uses at most 4 `_PointAtTime` samples. The extra
/// samples are there to make the `VelocityEstimate.offset` sufficiently large
/// to be recognized as a fling. See
/// `VerticalDragGestureRecognizer.isFlingGesture`.
static
const
int
_sampleSize
=
20
;
final
List
<
_PointAtTime
?>
_touchSamples
=
List
<
_PointAtTime
?>.
filled
(
_sampleSize
,
null
,
growable:
false
);
@override
void
addPosition
(
Duration
time
,
Offset
position
)
{
assert
(()
{
final
_PointAtTime
?
previousPoint
=
_touchSamples
[
_index
];
if
(
previousPoint
==
null
||
previousPoint
.
time
<=
time
)
return
true
;
throw
FlutterError
(
'The position being added (
$position
) has a smaller timestamp (
$time
)'
'than its predecessor:
$previousPoint
.'
);
}());
_index
=
(
_index
+
1
)
%
_sampleSize
;
_touchSamples
[
_index
]
=
_PointAtTime
(
position
,
time
);
}
// Computes the velocity using 2 adjacent points in history. When index = 0,
// it uses the latest point recorded and the point recorded immediately before
// it. The smaller index is, the ealier in history the points used are.
Offset
_previousVelocityAt
(
int
index
)
{
final
int
endIndex
=
(
_index
+
index
)
%
_sampleSize
;
final
int
startIndex
=
(
_index
+
index
-
1
)
%
_sampleSize
;
final
_PointAtTime
?
end
=
_touchSamples
[
endIndex
];
final
_PointAtTime
?
start
=
_touchSamples
[
startIndex
];
if
(
end
==
null
||
start
==
null
)
{
return
Offset
.
zero
;
}
final
int
dt
=
(
end
.
time
-
start
.
time
).
inMicroseconds
;
assert
(
dt
>=
0
);
return
dt
>
0
// Convert dt to milliseconds to preserve floating point precision.
?
(
end
.
point
-
start
.
point
)
*
1000
/
(
dt
.
toDouble
()
/
1000
)
:
Offset
.
zero
;
}
@override
VelocityEstimate
getVelocityEstimate
()
{
// The velocity estimated using this expression is an aproximation of the
// scroll velocity of an iOS scroll view at the moment the user touch was
// released, not the final velocity of the iOS pan gesture recognizer
// installed on the scroll view would report. Typically in an iOS scroll
// view the velocity values are different between the two, because the
// scroll view usually slows down when the touch is released.
final
Offset
estimatedVelocity
=
_previousVelocityAt
(-
2
)
*
0.6
+
_previousVelocityAt
(-
1
)
*
0.35
+
_previousVelocityAt
(
0
)
*
0.05
;
final
_PointAtTime
?
newestSample
=
_touchSamples
[
_index
];
_PointAtTime
?
oldestNonNullSample
;
for
(
int
i
=
1
;
i
<=
_sampleSize
;
i
+=
1
)
{
oldestNonNullSample
=
_touchSamples
[(
_index
+
i
)
%
_sampleSize
];
if
(
oldestNonNullSample
!=
null
)
break
;
}
if
(
oldestNonNullSample
==
null
||
newestSample
==
null
)
{
assert
(
false
,
'There must be at least 1 point in _touchSamples:
$_touchSamples
'
);
return
const
VelocityEstimate
(
pixelsPerSecond:
Offset
.
zero
,
confidence:
0.0
,
duration:
Duration
.
zero
,
offset:
Offset
.
zero
,
);
}
else
{
return
VelocityEstimate
(
pixelsPerSecond:
estimatedVelocity
,
confidence:
1.0
,
duration:
newestSample
.
time
-
oldestNonNullSample
.
time
,
offset:
newestSample
.
point
-
oldestNonNullSample
.
point
,
);
}
}
}
packages/flutter/lib/src/widgets/scroll_configuration.dart
View file @
cea055ef
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
// @dart = 2.8
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
import
'framework.dart'
;
...
@@ -52,6 +53,37 @@ class ScrollBehavior {
...
@@ -52,6 +53,37 @@ class ScrollBehavior {
return
null
;
return
null
;
}
}
/// Specifies the type of velocity tracker to use in the descendant
/// [Scrollable]s' drag gesture recognizers, for estimating the velocity of a
/// drag gesture.
///
/// This can be used to, for example, apply different fling velocity
/// estimation methods on different platforms, in order to match the
/// platform's native behavior.
///
/// Typically, the provided [GestureVelocityTrackerBuilder] should return a
/// fresh velocity tracker. If null is returned, [Scrollable] creates a new
/// [VelocityTracker] to track the newly added pointer that may develop into
/// a drag gesture.
///
/// The default implementation provides a new
/// [IOSScrollViewFlingVelocityTracker] on iOS and macOS for each new pointer,
/// and a new [VelocityTracker] on other platforms for each new pointer.
GestureVelocityTrackerBuilder
velocityTrackerBuilder
(
BuildContext
context
)
{
switch
(
getPlatform
(
context
))
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
return
(
PointerEvent
ev
)
=>
IOSScrollViewFlingVelocityTracker
();
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
return
(
PointerEvent
ev
)
=>
VelocityTracker
();
}
assert
(
false
);
return
(
PointerEvent
ev
)
=>
VelocityTracker
();
}
static
const
ScrollPhysics
_bouncingPhysics
=
BouncingScrollPhysics
(
parent:
RangeMaintainingScrollPhysics
());
static
const
ScrollPhysics
_bouncingPhysics
=
BouncingScrollPhysics
(
parent:
RangeMaintainingScrollPhysics
());
static
const
ScrollPhysics
_clampingPhysics
=
ClampingScrollPhysics
(
parent:
RangeMaintainingScrollPhysics
());
static
const
ScrollPhysics
_clampingPhysics
=
ClampingScrollPhysics
(
parent:
RangeMaintainingScrollPhysics
());
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
cea055ef
...
@@ -507,6 +507,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
...
@@ -507,6 +507,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
..
minFlingDistance
=
_physics
?.
minFlingDistance
..
minFlingDistance
=
_physics
?.
minFlingDistance
..
minFlingVelocity
=
_physics
?.
minFlingVelocity
..
minFlingVelocity
=
_physics
?.
minFlingVelocity
..
maxFlingVelocity
=
_physics
?.
maxFlingVelocity
..
maxFlingVelocity
=
_physics
?.
maxFlingVelocity
..
velocityTrackerBuilder
=
_configuration
.
velocityTrackerBuilder
(
context
)
..
dragStartBehavior
=
widget
.
dragStartBehavior
;
..
dragStartBehavior
=
widget
.
dragStartBehavior
;
},
},
),
),
...
@@ -526,6 +527,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
...
@@ -526,6 +527,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
..
minFlingDistance
=
_physics
?.
minFlingDistance
..
minFlingDistance
=
_physics
?.
minFlingDistance
..
minFlingVelocity
=
_physics
?.
minFlingVelocity
..
minFlingVelocity
=
_physics
?.
minFlingVelocity
..
maxFlingVelocity
=
_physics
?.
maxFlingVelocity
..
maxFlingVelocity
=
_physics
?.
maxFlingVelocity
..
velocityTrackerBuilder
=
_configuration
.
velocityTrackerBuilder
(
context
)
..
dragStartBehavior
=
widget
.
dragStartBehavior
;
..
dragStartBehavior
=
widget
.
dragStartBehavior
;
},
},
),
),
...
...
packages/flutter/test/gestures/velocity_tracker_test.dart
View file @
cea055ef
...
@@ -78,4 +78,71 @@ void main() {
...
@@ -78,4 +78,71 @@ void main() {
final
VelocityTracker
tracker
=
VelocityTracker
();
final
VelocityTracker
tracker
=
VelocityTracker
();
expect
(
tracker
.
getVelocity
(),
Velocity
.
zero
);
expect
(
tracker
.
getVelocity
(),
Velocity
.
zero
);
});
});
test
(
'FreeScrollStartVelocityTracker.getVelocity throws when no points'
,
()
{
final
IOSScrollViewFlingVelocityTracker
tracker
=
IOSScrollViewFlingVelocityTracker
();
AssertionError
exception
;
try
{
tracker
.
getVelocity
();
}
on
AssertionError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
?.
toString
(),
contains
(
'at least 1 point'
));
});
test
(
'FreeScrollStartVelocityTracker.getVelocity throws when the new point precedes the previous point'
,
()
{
final
IOSScrollViewFlingVelocityTracker
tracker
=
IOSScrollViewFlingVelocityTracker
();
AssertionError
exception
;
tracker
.
addPosition
(
const
Duration
(
hours:
1
),
Offset
.
zero
);
try
{
tracker
.
getVelocity
();
tracker
.
addPosition
(
const
Duration
(
seconds:
1
),
Offset
.
zero
);
}
on
AssertionError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
?.
toString
(),
contains
(
'has a smaller timestamp'
));
});
test
(
'Estimate does not throw when there are more than 1 point'
,
()
{
final
IOSScrollViewFlingVelocityTracker
tracker
=
IOSScrollViewFlingVelocityTracker
();
Offset
position
=
Offset
.
zero
;
Duration
time
=
Duration
.
zero
;
const
Offset
positionDelta
=
Offset
(
0
,
-
1
);
const
Duration
durationDelta
=
Duration
(
seconds:
1
);
AssertionError
exception
;
for
(
int
i
=
0
;
i
<
5
;
i
+=
1
)
{
position
+=
positionDelta
;
time
+=
durationDelta
;
tracker
.
addPosition
(
time
,
position
);
try
{
tracker
.
getVelocity
();
}
on
AssertionError
catch
(
e
)
{
exception
=
e
;
}
expect
(
exception
,
isNull
);
}
});
test
(
'Makes consistent velocity estimates with consistent velocity'
,
()
{
final
IOSScrollViewFlingVelocityTracker
tracker
=
IOSScrollViewFlingVelocityTracker
();
Offset
position
=
Offset
.
zero
;
Duration
time
=
Duration
.
zero
;
const
Offset
positionDelta
=
Offset
(
0
,
-
1
);
const
Duration
durationDelta
=
Duration
(
seconds:
1
);
for
(
int
i
=
0
;
i
<
10
;
i
+=
1
)
{
position
+=
positionDelta
;
time
+=
durationDelta
;
tracker
.
addPosition
(
time
,
position
);
if
(
i
>=
3
)
{
expect
(
tracker
.
getVelocity
().
pixelsPerSecond
,
positionDelta
);
}
}
});
}
}
packages/flutter/test/widgets/scroll_behavior_test.dart
View file @
cea055ef
...
@@ -4,9 +4,11 @@
...
@@ -4,9 +4,11 @@
// @dart = 2.8
// @dart = 2.8
import
'package:flutter/gestures.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
GestureVelocityTrackerBuilder
lastCreatedBuilder
;
class
TestScrollBehavior
extends
ScrollBehavior
{
class
TestScrollBehavior
extends
ScrollBehavior
{
const
TestScrollBehavior
(
this
.
flag
);
const
TestScrollBehavior
(
this
.
flag
);
...
@@ -21,6 +23,14 @@ class TestScrollBehavior extends ScrollBehavior {
...
@@ -21,6 +23,14 @@ class TestScrollBehavior extends ScrollBehavior {
@override
@override
bool
shouldNotify
(
TestScrollBehavior
old
)
=>
flag
!=
old
.
flag
;
bool
shouldNotify
(
TestScrollBehavior
old
)
=>
flag
!=
old
.
flag
;
@override
GestureVelocityTrackerBuilder
velocityTrackerBuilder
(
BuildContext
context
)
{
lastCreatedBuilder
=
flag
?
(
PointerEvent
ev
)
=>
VelocityTracker
()
:
(
PointerEvent
ev
)
=>
IOSScrollViewFlingVelocityTracker
();
return
lastCreatedBuilder
;
}
}
}
void
main
(
)
{
void
main
(
)
{
...
@@ -50,6 +60,7 @@ void main() {
...
@@ -50,6 +60,7 @@ void main() {
expect
(
behavior
,
isNotNull
);
expect
(
behavior
,
isNotNull
);
expect
(
behavior
.
flag
,
isTrue
);
expect
(
behavior
.
flag
,
isTrue
);
expect
(
position
.
physics
,
isA
<
ClampingScrollPhysics
>());
expect
(
position
.
physics
,
isA
<
ClampingScrollPhysics
>());
expect
(
lastCreatedBuilder
(
const
PointerDownEvent
()),
isA
<
VelocityTracker
>());
ScrollMetrics
metrics
=
position
.
copyWith
();
ScrollMetrics
metrics
=
position
.
copyWith
();
expect
(
metrics
.
extentAfter
,
equals
(
400.0
));
expect
(
metrics
.
extentAfter
,
equals
(
400.0
));
expect
(
metrics
.
viewportDimension
,
equals
(
600.0
));
expect
(
metrics
.
viewportDimension
,
equals
(
600.0
));
...
@@ -65,6 +76,7 @@ void main() {
...
@@ -65,6 +76,7 @@ void main() {
expect
(
behavior
,
isNotNull
);
expect
(
behavior
,
isNotNull
);
expect
(
behavior
.
flag
,
isFalse
);
expect
(
behavior
.
flag
,
isFalse
);
expect
(
position
.
physics
,
isA
<
BouncingScrollPhysics
>());
expect
(
position
.
physics
,
isA
<
BouncingScrollPhysics
>());
expect
(
lastCreatedBuilder
(
const
PointerDownEvent
()),
isA
<
IOSScrollViewFlingVelocityTracker
>());
// Regression test for https://github.com/flutter/flutter/issues/5856
// Regression test for https://github.com/flutter/flutter/issues/5856
metrics
=
position
.
copyWith
();
metrics
=
position
.
copyWith
();
expect
(
metrics
.
extentAfter
,
equals
(
400.0
));
expect
(
metrics
.
extentAfter
,
equals
(
400.0
));
...
...
packages/flutter/test/widgets/scrollable_test.dart
View file @
cea055ef
...
@@ -182,8 +182,7 @@ void main() {
...
@@ -182,8 +182,7 @@ void main() {
await
tester
.
pump
();
await
tester
.
pump
();
// The only applied velocity to the scrollable is the second fling that was in the
// The only applied velocity to the scrollable is the second fling that was in the
// opposite direction.
// opposite direction.
expect
(
getScrollVelocity
(
tester
),
greaterThan
(-
1000.0
));
expect
(
getScrollVelocity
(
tester
),
-
1000.0
);
expect
(
getScrollVelocity
(
tester
),
lessThan
(
0.0
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'No iOS/macOS momentum kept on hold gestures'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'No iOS/macOS momentum kept on hold gestures'
,
(
WidgetTester
tester
)
async
{
...
@@ -269,7 +268,7 @@ void main() {
...
@@ -269,7 +268,7 @@ void main() {
expect
(
getScrollOffset
(
tester
),
30.0
);
expect
(
getScrollOffset
(
tester
),
30.0
);
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
0.5
),
timeStamp:
const
Duration
(
milliseconds:
20
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
0.5
),
timeStamp:
const
Duration
(
milliseconds:
20
));
expect
(
getScrollOffset
(
tester
),
30.5
);
expect
(
getScrollOffset
(
tester
),
30.5
);
await
gesture
.
moveBy
(
Offset
.
zero
);
await
gesture
.
moveBy
(
Offset
.
zero
,
timeStamp:
const
Duration
(
milliseconds:
21
)
);
// Stationary too long, threshold reset.
// Stationary too long, threshold reset.
await
gesture
.
moveBy
(
Offset
.
zero
,
timeStamp:
const
Duration
(
milliseconds:
120
));
await
gesture
.
moveBy
(
Offset
.
zero
,
timeStamp:
const
Duration
(
milliseconds:
120
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
1.0
),
timeStamp:
const
Duration
(
milliseconds:
140
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
1.0
),
timeStamp:
const
Duration
(
milliseconds:
140
));
...
...
packages/flutter_test/lib/src/controller.dart
View file @
cea055ef
...
@@ -380,25 +380,25 @@ abstract class WidgetController {
...
@@ -380,25 +380,25 @@ abstract class WidgetController {
final
TestPointer
testPointer
=
TestPointer
(
pointer
??
_getNextPointer
(),
PointerDeviceKind
.
touch
,
null
,
buttons
);
final
TestPointer
testPointer
=
TestPointer
(
pointer
??
_getNextPointer
(),
PointerDeviceKind
.
touch
,
null
,
buttons
);
final
HitTestResult
result
=
hitTestOnBinding
(
startLocation
);
final
HitTestResult
result
=
hitTestOnBinding
(
startLocation
);
const
int
kMoveCount
=
50
;
// Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
const
int
kMoveCount
=
50
;
// Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final
double
timeStampDelta
=
1000.0
*
offset
.
distance
/
(
kMoveCount
*
speed
);
final
double
timeStampDelta
=
1000
000
.0
*
offset
.
distance
/
(
kMoveCount
*
speed
);
double
timeStamp
=
0.0
;
double
timeStamp
=
0.0
;
double
lastTimeStamp
=
timeStamp
;
double
lastTimeStamp
=
timeStamp
;
await
sendEventToBinding
(
testPointer
.
down
(
startLocation
,
timeStamp:
Duration
(
mi
lli
seconds:
timeStamp
.
round
())),
result
);
await
sendEventToBinding
(
testPointer
.
down
(
startLocation
,
timeStamp:
Duration
(
mi
cro
seconds:
timeStamp
.
round
())),
result
);
if
(
initialOffset
.
distance
>
0.0
)
{
if
(
initialOffset
.
distance
>
0.0
)
{
await
sendEventToBinding
(
testPointer
.
move
(
startLocation
+
initialOffset
,
timeStamp:
Duration
(
mi
lli
seconds:
timeStamp
.
round
())),
result
);
await
sendEventToBinding
(
testPointer
.
move
(
startLocation
+
initialOffset
,
timeStamp:
Duration
(
mi
cro
seconds:
timeStamp
.
round
())),
result
);
timeStamp
+=
initialOffsetDelay
.
inMi
lli
seconds
;
timeStamp
+=
initialOffsetDelay
.
inMi
cro
seconds
;
await
pump
(
initialOffsetDelay
);
await
pump
(
initialOffsetDelay
);
}
}
for
(
int
i
=
0
;
i
<=
kMoveCount
;
i
+=
1
)
{
for
(
int
i
=
0
;
i
<=
kMoveCount
;
i
+=
1
)
{
final
Offset
location
=
startLocation
+
initialOffset
+
Offset
.
lerp
(
Offset
.
zero
,
offset
,
i
/
kMoveCount
);
final
Offset
location
=
startLocation
+
initialOffset
+
Offset
.
lerp
(
Offset
.
zero
,
offset
,
i
/
kMoveCount
);
await
sendEventToBinding
(
testPointer
.
move
(
location
,
timeStamp:
Duration
(
mi
lli
seconds:
timeStamp
.
round
())),
result
);
await
sendEventToBinding
(
testPointer
.
move
(
location
,
timeStamp:
Duration
(
mi
cro
seconds:
timeStamp
.
round
())),
result
);
timeStamp
+=
timeStampDelta
;
timeStamp
+=
timeStampDelta
;
if
(
timeStamp
-
lastTimeStamp
>
frameInterval
.
inMi
lli
seconds
)
{
if
(
timeStamp
-
lastTimeStamp
>
frameInterval
.
inMi
cro
seconds
)
{
await
pump
(
Duration
(
mi
lli
seconds:
(
timeStamp
-
lastTimeStamp
).
truncate
()));
await
pump
(
Duration
(
mi
cro
seconds:
(
timeStamp
-
lastTimeStamp
).
truncate
()));
lastTimeStamp
=
timeStamp
;
lastTimeStamp
=
timeStamp
;
}
}
}
}
await
sendEventToBinding
(
testPointer
.
up
(
timeStamp:
Duration
(
mi
lli
seconds:
timeStamp
.
round
())),
result
);
await
sendEventToBinding
(
testPointer
.
up
(
timeStamp:
Duration
(
mi
cro
seconds:
timeStamp
.
round
())),
result
);
});
});
}
}
...
...
packages/flutter_test/test/controller_test.dart
View file @
cea055ef
...
@@ -550,6 +550,33 @@ void main() {
...
@@ -550,6 +550,33 @@ void main() {
},
},
);
);
testWidgets
(
'WidgetTester.fling produces strictly monotonically increasing timestamps, '
'when given a large velocity'
,
(
WidgetTester
tester
)
async
{
// Velocity trackers may misbehave if the `PointerMoveEvent`s' have the
// same timestamp. This is more likely to happen when the velocity tracker
// has a small sample size.
final
List
<
Duration
>
logs
=
<
Duration
>[];
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Listener
(
onPointerMove:
(
PointerMoveEvent
event
)
=>
logs
.
add
(
event
.
timeStamp
),
child:
const
Text
(
'test'
),
),
),
);
await
tester
.
fling
(
find
.
text
(
'test'
),
const
Offset
(
0.0
,
-
50.0
),
10000.0
);
await
tester
.
pumpAndSettle
();
for
(
int
i
=
0
;
i
+
1
<
logs
.
length
;
i
+=
1
)
{
expect
(
logs
[
i
+
1
],
greaterThan
(
logs
[
i
]));
}
});
testWidgets
(
testWidgets
(
'ensureVisible: scrolls to make widget visible'
,
'ensureVisible: scrolls to make widget visible'
,
(
WidgetTester
tester
)
async
{
(
WidgetTester
tester
)
async
{
...
...
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