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
74e93e4c
Commit
74e93e4c
authored
Sep 19, 2018
by
Renan
Committed by
Ian Hickson
Sep 19, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make ScaleGestureRecognizer detect pointer rotation (#17345)
parent
ff1f8dd1
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
292 additions
and
6 deletions
+292
-6
scale.dart
packages/flutter/lib/src/gestures/scale.dart
+103
-6
scale_test.dart
packages/flutter/test/gestures/scale_test.dart
+189
-0
No files found.
packages/flutter/lib/src/gestures/scale.dart
View file @
74e93e4c
...
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'arena.dart'
;
import
'constants.dart'
;
import
'events.dart'
;
...
...
@@ -47,13 +49,15 @@ class ScaleStartDetails {
class
ScaleUpdateDetails
{
/// Creates details for [GestureScaleUpdateCallback].
///
/// The [focalPoint]
and [scale
] arguments must not be null. The [scale]
/// The [focalPoint]
, [scale], [rotation
] arguments must not be null. The [scale]
/// argument must be greater than or equal to zero.
ScaleUpdateDetails
({
this
.
focalPoint
=
Offset
.
zero
,
this
.
scale
=
1.0
,
this
.
rotation
=
0.0
,
})
:
assert
(
focalPoint
!=
null
),
assert
(
scale
!=
null
&&
scale
>=
0.0
);
assert
(
scale
!=
null
&&
scale
>=
0.0
),
assert
(
rotation
!=
null
);
/// The focal point of the pointers in contact with the screen. Reported in
/// global coordinates.
...
...
@@ -63,8 +67,12 @@ class ScaleUpdateDetails {
/// greater than or equal to zero.
final
double
scale
;
/// The angle implied by the first two pointers to enter in contact with
/// the screen. Expressed in radians.
final
double
rotation
;
@override
String
toString
()
=>
'ScaleUpdateDetails(focalPoint:
$focalPoint
, scale:
$scale
)'
;
String
toString
()
=>
'ScaleUpdateDetails(focalPoint:
$focalPoint
, scale:
$scale
, rotation:
$rotation
)'
;
}
/// Details for [GestureScaleEndCallback].
...
...
@@ -99,11 +107,41 @@ bool _isFlingGesture(Velocity velocity) {
return
speedSquared
>
kMinFlingVelocity
*
kMinFlingVelocity
;
}
/// Defines a line between two pointers on screen.
///
/// [_LineBetweenPointers] is an abstraction of a line between two pointers in
/// contact with the screen. Used to track the rotation of a scale gesture.
class
_LineBetweenPointers
{
/// Creates a [_LineBetweenPointers]. None of the [pointerStartLocation], [pointerStartId]
/// [pointerEndLocation] and [pointerEndId] must be null. [pointerStartId] and [pointerEndId]
/// should be different.
_LineBetweenPointers
({
this
.
pointerStartLocation
=
Offset
.
zero
,
this
.
pointerStartId
=
0
,
this
.
pointerEndLocation
=
Offset
.
zero
,
this
.
pointerEndId
=
1
})
:
assert
(
pointerStartLocation
!=
null
&&
pointerEndLocation
!=
null
),
assert
(
pointerStartId
!=
null
&&
pointerEndId
!=
null
),
assert
(
pointerStartId
!=
pointerEndId
);
// The location and the id of the pointer that marks the start of the line.
final
Offset
pointerStartLocation
;
final
int
pointerStartId
;
// The location and the id of the pointer that marks the end of the line.
final
Offset
pointerEndLocation
;
final
int
pointerEndId
;
}
/// Recognizes a scale gesture.
///
/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
/// calculates their focal point
and indicated scale
. When a focal pointer is
/// established, the recognizer calls [onStart]. As the focal point
and scale
/// calculates their focal point
, indicated scale and rotation
. When a focal pointer is
/// established, the recognizer calls [onStart]. As the focal point
, scale, rotation
/// change, the recognizer calls [onUpdate]. When the pointers are no longer in
/// contact with the screen, the recognizer calls [onEnd].
class
ScaleGestureRecognizer
extends
OneSequenceGestureRecognizer
{
...
...
@@ -127,11 +165,34 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
Offset
_currentFocalPoint
;
double
_initialSpan
;
double
_currentSpan
;
_LineBetweenPointers
_initialLine
;
_LineBetweenPointers
_currentLine
;
Map
<
int
,
Offset
>
_pointerLocations
;
List
<
int
>
_pointerQueue
;
/// A queue to sort pointers in order of entrance
final
Map
<
int
,
VelocityTracker
>
_velocityTrackers
=
<
int
,
VelocityTracker
>{};
double
get
_scaleFactor
=>
_initialSpan
>
0.0
?
_currentSpan
/
_initialSpan
:
1.0
;
double
_computeRotationFactor
()
{
if
(
_initialLine
==
null
||
_currentLine
==
null
)
{
return
0.0
;
}
final
double
fx
=
_initialLine
.
pointerStartLocation
.
dx
;
final
double
fy
=
_initialLine
.
pointerStartLocation
.
dy
;
final
double
sx
=
_initialLine
.
pointerEndLocation
.
dx
;
final
double
sy
=
_initialLine
.
pointerEndLocation
.
dy
;
final
double
nfx
=
_currentLine
.
pointerStartLocation
.
dx
;
final
double
nfy
=
_currentLine
.
pointerStartLocation
.
dy
;
final
double
nsx
=
_currentLine
.
pointerEndLocation
.
dx
;
final
double
nsy
=
_currentLine
.
pointerEndLocation
.
dy
;
final
double
angle1
=
math
.
atan2
(
fy
-
sy
,
fx
-
sx
);
final
double
angle2
=
math
.
atan2
(
nfy
-
nsy
,
nfx
-
nsx
);
return
angle2
-
angle1
;
}
@override
void
addPointer
(
PointerEvent
event
)
{
startTrackingPointer
(
event
.
pointer
);
...
...
@@ -141,6 +202,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_initialSpan
=
0.0
;
_currentSpan
=
0.0
;
_pointerLocations
=
<
int
,
Offset
>{};
_pointerQueue
=
<
int
>[];
}
}
...
...
@@ -158,14 +220,18 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
shouldStartIfAccepted
=
true
;
}
else
if
(
event
is
PointerDownEvent
)
{
_pointerLocations
[
event
.
pointer
]
=
event
.
position
;
_pointerQueue
.
add
(
event
.
pointer
);
didChangeConfiguration
=
true
;
shouldStartIfAccepted
=
true
;
}
else
if
(
event
is
PointerUpEvent
||
event
is
PointerCancelEvent
)
{
_pointerLocations
.
remove
(
event
.
pointer
);
_pointerQueue
.
remove
(
event
.
pointer
);
didChangeConfiguration
=
true
;
}
_updateLines
();
_update
();
if
(!
didChangeConfiguration
||
_reconfigure
(
event
.
pointer
))
_advanceStateMachine
(
shouldStartIfAccepted
);
stopTrackingIfPointerNoLongerDown
(
event
);
...
...
@@ -187,9 +253,40 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_currentSpan
=
count
>
0
?
totalDeviation
/
count
:
0.0
;
}
/// Updates [_initialLine] and [_currentLine] accordingly to the situation of
/// the registered pointers
void
_updateLines
()
{
final
int
count
=
_pointerLocations
.
keys
.
length
;
assert
(
_pointerQueue
.
length
>=
count
);
/// In case of just one pointer registered, reconfigure [_initialLine]
if
(
count
<
2
)
{
_initialLine
=
_currentLine
;
}
else
if
(
_initialLine
!=
null
&&
_initialLine
.
pointerStartId
==
_pointerQueue
[
0
]
&&
_initialLine
.
pointerEndId
==
_pointerQueue
[
1
])
{
/// Rotation updated, set the [_currentLine]
_currentLine
=
_LineBetweenPointers
(
pointerStartId:
_pointerQueue
[
0
],
pointerStartLocation:
_pointerLocations
[
_pointerQueue
[
0
]],
pointerEndId:
_pointerQueue
[
1
],
pointerEndLocation:
_pointerLocations
[
_pointerQueue
[
1
]]
);
}
else
{
/// A new rotation process is on the way, set the [_initialLine]
_initialLine
=
_LineBetweenPointers
(
pointerStartId:
_pointerQueue
[
0
],
pointerStartLocation:
_pointerLocations
[
_pointerQueue
[
0
]],
pointerEndId:
_pointerQueue
[
1
],
pointerEndLocation:
_pointerLocations
[
_pointerQueue
[
1
]]
);
_currentLine
=
null
;
}
}
bool
_reconfigure
(
int
pointer
)
{
_initialFocalPoint
=
_currentFocalPoint
;
_initialSpan
=
_currentSpan
;
_initialLine
=
_currentLine
;
if
(
_state
==
_ScaleState
.
started
)
{
if
(
onEnd
!=
null
)
{
final
VelocityTracker
tracker
=
_velocityTrackers
[
pointer
];
...
...
@@ -230,7 +327,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
}
if
(
_state
==
_ScaleState
.
started
&&
onUpdate
!=
null
)
invokeCallback
<
void
>(
'onUpdate'
,
()
=>
onUpdate
(
ScaleUpdateDetails
(
scale:
_scaleFactor
,
focalPoint:
_currentFocalPoint
)));
invokeCallback
<
void
>(
'onUpdate'
,
()
=>
onUpdate
(
ScaleUpdateDetails
(
scale:
_scaleFactor
,
focalPoint:
_currentFocalPoint
,
rotation:
_computeRotationFactor
()
)));
}
void
_dispatchOnStartCallbackIfNeeded
()
{
...
...
packages/flutter/test/gestures/scale_test.dart
View file @
74e93e4c
...
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/gestures.dart'
;
...
...
@@ -155,6 +157,18 @@ void main() {
expect
(
updatedScale
,
2.0
);
updatedScale
=
null
;
// Continue rotating with two fingers
tester
.
route
(
pointer3
.
move
(
const
Offset
(
30.0
,
40.0
)));
expect
(
updatedFocalPoint
,
const
Offset
(
25.0
,
35.0
));
updatedFocalPoint
=
null
;
expect
(
updatedScale
,
2.0
);
updatedScale
=
null
;
tester
.
route
(
pointer3
.
move
(
const
Offset
(
10.0
,
20.0
)));
expect
(
updatedFocalPoint
,
const
Offset
(
15.0
,
25.0
));
updatedFocalPoint
=
null
;
expect
(
updatedScale
,
2.0
);
updatedScale
=
null
;
tester
.
route
(
pointer2
.
up
());
expect
(
didStartScale
,
isFalse
);
expect
(
updatedFocalPoint
,
isNull
);
...
...
@@ -266,4 +280,179 @@ void main() {
scale
.
dispose
();
drag
.
dispose
();
});
testGesture
(
'Should recognize rotation gestures'
,
(
GestureTester
tester
)
{
final
ScaleGestureRecognizer
scale
=
ScaleGestureRecognizer
();
final
TapGestureRecognizer
tap
=
TapGestureRecognizer
();
bool
didStartScale
=
false
;
Offset
updatedFocalPoint
;
scale
.
onStart
=
(
ScaleStartDetails
details
)
{
didStartScale
=
true
;
updatedFocalPoint
=
details
.
focalPoint
;
};
double
updatedRotation
;
scale
.
onUpdate
=
(
ScaleUpdateDetails
details
)
{
updatedRotation
=
details
.
rotation
;
updatedFocalPoint
=
details
.
focalPoint
;
};
bool
didEndScale
=
false
;
scale
.
onEnd
=
(
ScaleEndDetails
details
)
{
didEndScale
=
true
;
};
bool
didTap
=
false
;
tap
.
onTap
=
()
{
didTap
=
true
;
};
final
TestPointer
pointer1
=
TestPointer
(
1
);
final
PointerDownEvent
down
=
pointer1
.
down
(
const
Offset
(
0.0
,
0.0
));
scale
.
addPointer
(
down
);
tap
.
addPointer
(
down
);
tester
.
closeArena
(
1
);
expect
(
didStartScale
,
isFalse
);
expect
(
updatedRotation
,
isNull
);
expect
(
updatedFocalPoint
,
isNull
);
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
tester
.
route
(
down
);
tester
.
route
(
pointer1
.
move
(
const
Offset
(
20.0
,
30.0
)));
expect
(
didStartScale
,
isTrue
);
didStartScale
=
false
;
expect
(
updatedFocalPoint
,
const
Offset
(
20.0
,
30.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
// Two-finger scaling
final
TestPointer
pointer2
=
TestPointer
(
2
);
final
PointerDownEvent
down2
=
pointer2
.
down
(
const
Offset
(
30.0
,
40.0
));
scale
.
addPointer
(
down2
);
tap
.
addPointer
(
down2
);
tester
.
closeArena
(
2
);
tester
.
route
(
down2
);
expect
(
didEndScale
,
isTrue
);
didEndScale
=
false
;
expect
(
updatedFocalPoint
,
isNull
);
expect
(
updatedRotation
,
isNull
);
expect
(
didStartScale
,
isFalse
);
// Zoom in
tester
.
route
(
pointer2
.
move
(
const
Offset
(
40.0
,
50.0
)));
expect
(
didStartScale
,
isTrue
);
didStartScale
=
false
;
expect
(
updatedFocalPoint
,
const
Offset
(
30.0
,
40.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
// Rotation
tester
.
route
(
pointer2
.
move
(
const
Offset
(
0.0
,
10.0
)));
expect
(
updatedFocalPoint
,
const
Offset
(
10.0
,
20.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
math
.
pi
);
updatedRotation
=
null
;
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
// Three-finger scaling
final
TestPointer
pointer3
=
TestPointer
(
3
);
final
PointerDownEvent
down3
=
pointer3
.
down
(
const
Offset
(
25.0
,
35.0
));
scale
.
addPointer
(
down3
);
tap
.
addPointer
(
down3
);
tester
.
closeArena
(
3
);
tester
.
route
(
down3
);
expect
(
didEndScale
,
isTrue
);
didEndScale
=
false
;
expect
(
updatedFocalPoint
,
isNull
);
expect
(
updatedRotation
,
isNull
);
expect
(
didStartScale
,
isFalse
);
// Zoom in
tester
.
route
(
pointer3
.
move
(
const
Offset
(
55.0
,
65.0
)));
expect
(
didStartScale
,
isTrue
);
didStartScale
=
false
;
expect
(
updatedFocalPoint
,
const
Offset
(
25.0
,
35.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
// Return to original positions but with different fingers
tester
.
route
(
pointer1
.
move
(
const
Offset
(
25.0
,
35.0
)));
tester
.
route
(
pointer2
.
move
(
const
Offset
(
20.0
,
30.0
)));
tester
.
route
(
pointer3
.
move
(
const
Offset
(
15.0
,
25.0
)));
expect
(
didStartScale
,
isFalse
);
expect
(
updatedFocalPoint
,
const
Offset
(
20.0
,
30.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
expect
(
didEndScale
,
isFalse
);
expect
(
didTap
,
isFalse
);
tester
.
route
(
pointer1
.
up
());
expect
(
didStartScale
,
isFalse
);
expect
(
updatedFocalPoint
,
isNull
);
expect
(
updatedRotation
,
isNull
);
expect
(
didEndScale
,
isTrue
);
didEndScale
=
false
;
expect
(
didTap
,
isFalse
);
// Continue scaling with two fingers
tester
.
route
(
pointer3
.
move
(
const
Offset
(
10.0
,
20.0
)));
expect
(
didStartScale
,
isTrue
);
didStartScale
=
false
;
expect
(
updatedFocalPoint
,
const
Offset
(
15.0
,
25.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
// Continue rotating with two fingers
tester
.
route
(
pointer3
.
move
(
const
Offset
(
30.0
,
40.0
)));
expect
(
updatedFocalPoint
,
const
Offset
(
25.0
,
35.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
-
math
.
pi
);
updatedRotation
=
null
;
tester
.
route
(
pointer3
.
move
(
const
Offset
(
10.0
,
20.0
)));
expect
(
updatedFocalPoint
,
const
Offset
(
15.0
,
25.0
));
updatedFocalPoint
=
null
;
expect
(
updatedRotation
,
0.0
);
updatedRotation
=
null
;
tester
.
route
(
pointer2
.
up
());
expect
(
didStartScale
,
isFalse
);
expect
(
updatedFocalPoint
,
isNull
);
expect
(
updatedRotation
,
isNull
);
expect
(
didEndScale
,
isTrue
);
didEndScale
=
false
;
expect
(
didTap
,
isFalse
);
// We are done
tester
.
route
(
pointer3
.
up
());
expect
(
didStartScale
,
isFalse
);
expect
(
updatedFocalPoint
,
isNull
);
expect
(
updatedRotation
,
isNull
);
expect
(
didEndScale
,
isFalse
);
didEndScale
=
false
;
expect
(
didTap
,
isFalse
);
scale
.
dispose
();
tap
.
dispose
();
});
}
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