mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Adds force press gesture detector and recognizer (#24554)
* adds Force Press gesture detector and recognizer
This commit is contained in:
parent
bbddade17f
commit
3d8aec2b99
|
@ -16,6 +16,7 @@ export 'src/gestures/drag.dart';
|
|||
export 'src/gestures/drag_details.dart';
|
||||
export 'src/gestures/eager.dart';
|
||||
export 'src/gestures/events.dart';
|
||||
export 'src/gestures/force_press.dart';
|
||||
export 'src/gestures/hit_test.dart';
|
||||
export 'src/gestures/long_press.dart';
|
||||
export 'src/gestures/lsq_solver.dart';
|
||||
|
|
|
@ -334,6 +334,7 @@ class PointerAddedEvent extends PointerEvent {
|
|||
int device = 0,
|
||||
Offset position = Offset.zero,
|
||||
bool obscured = false,
|
||||
double pressure = 0.0,
|
||||
double pressureMin = 1.0,
|
||||
double pressureMax = 1.0,
|
||||
double distance = 0.0,
|
||||
|
@ -348,6 +349,7 @@ class PointerAddedEvent extends PointerEvent {
|
|||
device: device,
|
||||
position: position,
|
||||
obscured: obscured,
|
||||
pressure: pressure,
|
||||
pressureMin: pressureMin,
|
||||
pressureMax: pressureMax,
|
||||
distance: distance,
|
||||
|
@ -372,6 +374,7 @@ class PointerRemovedEvent extends PointerEvent {
|
|||
PointerDeviceKind kind = PointerDeviceKind.touch,
|
||||
int device = 0,
|
||||
bool obscured = false,
|
||||
double pressure = 0.0,
|
||||
double pressureMin = 1.0,
|
||||
double pressureMax = 1.0,
|
||||
double distanceMax = 0.0,
|
||||
|
@ -383,11 +386,12 @@ class PointerRemovedEvent extends PointerEvent {
|
|||
device: device,
|
||||
position: null,
|
||||
obscured: obscured,
|
||||
pressure: pressure,
|
||||
pressureMin: pressureMin,
|
||||
pressureMax: pressureMax,
|
||||
distanceMax: distanceMax,
|
||||
radiusMin: radiusMin,
|
||||
radiusMax: radiusMax
|
||||
radiusMax: radiusMax,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -410,6 +414,7 @@ class PointerHoverEvent extends PointerEvent {
|
|||
Offset delta = Offset.zero,
|
||||
int buttons = 0,
|
||||
bool obscured = false,
|
||||
double pressure = 0.0,
|
||||
double pressureMin = 1.0,
|
||||
double pressureMax = 1.0,
|
||||
double distance = 0.0,
|
||||
|
@ -431,6 +436,7 @@ class PointerHoverEvent extends PointerEvent {
|
|||
buttons: buttons,
|
||||
down: false,
|
||||
obscured: obscured,
|
||||
pressure: pressure,
|
||||
pressureMin: pressureMin,
|
||||
pressureMax: pressureMax,
|
||||
distance: distance,
|
||||
|
@ -567,7 +573,7 @@ class PointerUpEvent extends PointerEvent {
|
|||
Offset position = Offset.zero,
|
||||
int buttons = 0,
|
||||
bool obscured = false,
|
||||
double pressure = 1.0,
|
||||
double pressure = 0.0,
|
||||
double pressureMin = 1.0,
|
||||
double pressureMax = 1.0,
|
||||
double distance = 0.0,
|
||||
|
@ -616,6 +622,7 @@ class PointerCancelEvent extends PointerEvent {
|
|||
Offset position = Offset.zero,
|
||||
int buttons = 0,
|
||||
bool obscured = false,
|
||||
double pressure = 0.0,
|
||||
double pressureMin = 1.0,
|
||||
double pressureMax = 1.0,
|
||||
double distance = 0.0,
|
||||
|
@ -636,6 +643,7 @@ class PointerCancelEvent extends PointerEvent {
|
|||
buttons: buttons,
|
||||
down: false,
|
||||
obscured: obscured,
|
||||
pressure: pressure,
|
||||
pressureMin: pressureMin,
|
||||
pressureMax: pressureMax,
|
||||
distance: distance,
|
||||
|
|
308
packages/flutter/lib/src/gestures/force_press.dart
Normal file
308
packages/flutter/lib/src/gestures/force_press.dart
Normal file
|
@ -0,0 +1,308 @@
|
|||
// Copyright 2018 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:ui' show Offset;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'constants.dart';
|
||||
import 'events.dart';
|
||||
import 'recognizer.dart';
|
||||
|
||||
enum _ForceState {
|
||||
// No pointer has touched down and the detector is ready for a pointer down to occur.
|
||||
ready,
|
||||
|
||||
// A pointer has touched down, but a force press gesture has not yet been detected.
|
||||
possible,
|
||||
|
||||
// A pointer is down and a force press gesture has been detected. However, if
|
||||
// the ForcePressGestureRecognizer is the only recognizer in the arena, thus
|
||||
// accepted as soon as the gesture state is possible, the gesture will not
|
||||
// yet have started.
|
||||
accepted,
|
||||
|
||||
// A pointer is down and the gesture has started, ie. the pressure of the pointer
|
||||
// has just become greater than the ForcePressGestureRecognizer.startPressure.
|
||||
started,
|
||||
|
||||
// A pointer is down and the pressure of the pointer has just become greater
|
||||
// than the ForcePressGestureRecognizer.peakPressure. Even after a pointer
|
||||
// crosses this threshold, onUpdate callbacks will still be sent.
|
||||
peaked,
|
||||
}
|
||||
|
||||
/// Details object for callbacks that use [GestureForcePressStartCallback],
|
||||
/// [GestureForcePressPeakCallback], [GestureForcePressEndCallback] or
|
||||
/// [GestureForcePressUpdateCallback].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ForcePressGestureRecognizer.onStart], [ForcePressGestureRecognizer.onPeak],
|
||||
/// [ForcePressGestureRecognizer.onEnd], and [ForcePressGestureRecognizer.onUpdate]
|
||||
/// which use [ForcePressDetails].
|
||||
/// * [ForcePressUpdateDetails], the details for [ForcePressUpdateCallback].
|
||||
class ForcePressDetails {
|
||||
/// Creates details for a [GestureForcePressStartCallback],
|
||||
/// [GestureForcePressPeakCallback] or [GestureForcePressEndCallback].
|
||||
///
|
||||
/// The [globalPosition] argument must not be null.
|
||||
ForcePressDetails({
|
||||
@required this.globalPosition,
|
||||
@required this.pressure,
|
||||
}) : assert(globalPosition != null),
|
||||
assert(pressure != null);
|
||||
|
||||
/// The global position at which the function was called.
|
||||
final Offset globalPosition;
|
||||
|
||||
/// The pressure of the pointer on the screen.
|
||||
final double pressure;
|
||||
}
|
||||
|
||||
/// Signature used by a [ForcePressGestureRecognizer] for when a pointer has
|
||||
/// pressed with at least [ForcePressGestureRecognizer.startPressure].
|
||||
typedef GestureForcePressStartCallback = void Function(ForcePressDetails details);
|
||||
|
||||
/// Signature used by [ForcePressGestureRecognizer] for when a pointer that has
|
||||
/// pressed with at least [ForcePressGestureRecognizer.peakPressure].
|
||||
typedef GestureForcePressPeakCallback = void Function(ForcePressDetails details);
|
||||
|
||||
/// Signature used by [ForcePressGestureRecognizer] during the frames
|
||||
/// after the triggering of a [ForcePressGestureRecognizer.onStart] callback.
|
||||
typedef GestureForcePressUpdateCallback = void Function(ForcePressDetails details);
|
||||
|
||||
/// Signature for when the pointer that previously triggered a
|
||||
/// [ForcePressGestureRecognizer.onStart] callback is no longer in contact
|
||||
/// with the screen.
|
||||
typedef GestureForcePressEndCallback = void Function(ForcePressDetails details);
|
||||
|
||||
/// Signature used by [ForcePressGestureRecognizer] for interpolating the raw
|
||||
/// device pressure to a value in the range [0, 1] given the device's pressure
|
||||
/// min and pressure max.
|
||||
typedef GestureForceInterpolation = double Function(double pressureMin, double pressureMax, double pressure);
|
||||
|
||||
/// Recognizes a force press on devices that have force sensors.
|
||||
///
|
||||
/// Only the force from a single pointer is used to invoke events. A tap
|
||||
/// recognizer will win against this recognizer on pointer up as long as the
|
||||
/// pointer has not pressed with a force greater than
|
||||
/// [ForcePressGestureRecognizer.startPressure]. A long press recognizer will
|
||||
/// win when the press down time exceeds the threshold time as long as the
|
||||
/// pointer's pressure was never greater than
|
||||
/// [ForcePressGestureRecognizer.startPressure] in that duration.
|
||||
///
|
||||
/// As of November, 2018 iPhone devices of generation 6S and higher have
|
||||
/// force touch functionality, with the exception of the iPhone XR. In addition,
|
||||
/// a small handful of Android devices have this functionality as well.
|
||||
///
|
||||
/// Reported pressure will always be in the range [0.0, 1.0], where 1.0 is
|
||||
/// maximum pressure and 0.0 is minimum pressure. If using a non-linear
|
||||
/// interpolation equation, the pressure reported will correspond with the
|
||||
/// custom curve. (ie. if the interpolation maps t(0.5) -> 0.1, a value of 0.1
|
||||
/// will be reported at a device pressure value of 0.5).
|
||||
///
|
||||
class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
||||
/// Creates a force press gesture recognizer.
|
||||
///
|
||||
/// The [startPressure] defaults to 0.4, and [peakPressure] defaults to 0.85
|
||||
/// where a value of 0.0 is no pressure and a value of 1.0 is maximum pressure.
|
||||
///
|
||||
/// [startPressure], [peakPressure] and [interpolation] must not be null.
|
||||
/// [peakPressure] must be greater than [startPressure]. [interpolation] must
|
||||
/// always return a value in the range [0.0, 1.0] where
|
||||
/// pressureMin <= pressure <= pressureMax.
|
||||
ForcePressGestureRecognizer({
|
||||
this.startPressure = 0.4,
|
||||
this.peakPressure = 0.85,
|
||||
this.interpolation = _inverseLerp,
|
||||
Object debugOwner,
|
||||
}) : assert(startPressure != null),
|
||||
assert(peakPressure != null),
|
||||
assert(interpolation != null),
|
||||
assert(peakPressure > startPressure),
|
||||
super(debugOwner: debugOwner);
|
||||
|
||||
/// A pointer is in contact with the screen and has just pressed with a force
|
||||
/// exceeding the [startPressure]. Consequently, if there were other gesture
|
||||
/// detectors, only the force press gesture will be detected and all others
|
||||
/// will be rejected.
|
||||
///
|
||||
/// The position of the pointer is provided in the callback's `details`
|
||||
/// argument, which is a [ForcePressDetails] object.
|
||||
GestureForcePressStartCallback onStart;
|
||||
|
||||
/// A pointer is in contact with the screen and is either moving on the plane
|
||||
/// of the screen, pressing the screen with varying forces or both
|
||||
/// simultaneously.
|
||||
///
|
||||
/// This callback will be invoked for every pointer event after the invocation
|
||||
/// of [onStart] and/or [onPeak] and before the invocation of [onEnd], no
|
||||
/// matter what the pressure is during this time period. The position and
|
||||
/// pressure of the pointer is provided in the callback's `details` argument,
|
||||
/// which is a [ForcePressUpdateDetails] object.
|
||||
GestureForcePressUpdateCallback onUpdate;
|
||||
|
||||
/// A pointer is in contact with the screen and has just pressed with a force
|
||||
/// exceeding the [peakPressure]. This is an arbitrary second level action
|
||||
/// threshold and isn't necessarily the maximum possible device pressure
|
||||
/// (which is 1.0).
|
||||
///
|
||||
/// The position of the pointer is provided in the callback's `details`
|
||||
/// argument, which is a [ForcePressDetails] object.
|
||||
GestureForcePressPeakCallback onPeak;
|
||||
|
||||
/// A pointer is no longer in contact with the screen.
|
||||
///
|
||||
/// The position of the pointer is provided in the callback's `details`
|
||||
/// argument, which is a [ForcePressDetails] object.
|
||||
GestureForcePressEndCallback onEnd;
|
||||
|
||||
/// The pressure of the press required to initiate a force press.
|
||||
///
|
||||
/// A value of 0.0 is no pressure, and 1.0 is maximum pressure.
|
||||
final double startPressure;
|
||||
|
||||
/// The pressure of the press required to peak a force press.
|
||||
///
|
||||
/// A value of 0.0 is no pressure, and 1.0 is maximum pressure. This value
|
||||
/// must be greater than [startPressure].
|
||||
final double peakPressure;
|
||||
|
||||
/// The function used to convert the raw device pressure values into a value
|
||||
/// in the range [0, 1].
|
||||
///
|
||||
/// The function takes in the device's min, max and raw touch
|
||||
/// pressure and returns a value in the range [0.0, 1.0] denoting the
|
||||
/// interpolated touch pressure.
|
||||
///
|
||||
/// This function must always return values in the range [0, 1] when
|
||||
/// pressureMin <= pressure <= pressureMax.
|
||||
///
|
||||
/// By default, the the function is a simple linear interpolation, however,
|
||||
/// changing the function could be useful to accommodate variations in the way
|
||||
/// different devices respond to pressure, change how animations from pressure
|
||||
/// feedback are rendered or for other custom functionality.
|
||||
///
|
||||
/// For example, an ease in curve can be used to determine the interpolated
|
||||
/// value:
|
||||
///
|
||||
/// ```dart
|
||||
/// static double interpolateWithEasing(double min, double max, double t) {
|
||||
/// final double lerp = (t - min) / (max - min);
|
||||
/// return Curves.easeIn.transform(lerp);
|
||||
/// }
|
||||
/// ```
|
||||
final GestureForceInterpolation interpolation;
|
||||
|
||||
Offset _lastPosition;
|
||||
double _lastPressure;
|
||||
_ForceState _state = _ForceState.ready;
|
||||
|
||||
@override
|
||||
void addPointer(PointerEvent event) {
|
||||
startTrackingPointer(event.pointer);
|
||||
if (_state == _ForceState.ready) {
|
||||
_state = _ForceState.possible;
|
||||
_lastPosition = event.position;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event) {
|
||||
assert(_state != _ForceState.ready);
|
||||
// A static pointer with changes in pressure creates PointerMoveEvent events.
|
||||
if (event is PointerMoveEvent || event is PointerDownEvent) {
|
||||
final double pressure = interpolation(event.pressureMin, event.pressureMax, event.pressure);
|
||||
assert(pressure.isNaN ? true : (pressure <= 1.0 && pressure >= 0.0));
|
||||
_lastPosition = event.position;
|
||||
_lastPressure = pressure;
|
||||
|
||||
if (_state == _ForceState.possible) {
|
||||
if (pressure > startPressure) {
|
||||
_state = _ForceState.started;
|
||||
resolve(GestureDisposition.accepted);
|
||||
} else if (event.delta.distanceSquared > kTouchSlop) {
|
||||
resolve(GestureDisposition.rejected);
|
||||
}
|
||||
}
|
||||
// In case this is the only gesture detector we still don't want to start
|
||||
// the gesture until the pressure is greater than the startPressure.
|
||||
if (pressure > startPressure && _state == _ForceState.accepted) {
|
||||
_state = _ForceState.started;
|
||||
if (onStart != null) {
|
||||
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
||||
pressure: pressure,
|
||||
globalPosition: _lastPosition,
|
||||
)));
|
||||
}
|
||||
}
|
||||
if (onPeak != null && pressure > peakPressure &&
|
||||
(_state == _ForceState.started)) {
|
||||
_state = _ForceState.peaked;
|
||||
if (onPeak != null) {
|
||||
invokeCallback<void>('onPeak', () => onPeak(ForcePressDetails(
|
||||
pressure: pressure,
|
||||
globalPosition: event.position,
|
||||
)));
|
||||
}
|
||||
}
|
||||
if (onUpdate != null &&
|
||||
(_state == _ForceState.started || _state == _ForceState.peaked)) {
|
||||
if (onUpdate != null) {
|
||||
invokeCallback<void>('onUpdate', () => onUpdate(ForcePressDetails(
|
||||
pressure: pressure,
|
||||
globalPosition: event.position,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
stopTrackingIfPointerNoLongerDown(event);
|
||||
}
|
||||
|
||||
@override
|
||||
void acceptGesture(int pointer) {
|
||||
if (_state == _ForceState.possible)
|
||||
_state = _ForceState.accepted;
|
||||
|
||||
if (onStart != null && _state == _ForceState.started) {
|
||||
invokeCallback<void>('onStart', () => onStart(ForcePressDetails(
|
||||
pressure: _lastPressure,
|
||||
globalPosition: _lastPosition,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didStopTrackingLastPointer(int pointer) {
|
||||
final bool wasAccepted = _state == _ForceState.started || _state == _ForceState.peaked;
|
||||
if (_state == _ForceState.possible) {
|
||||
resolve(GestureDisposition.rejected);
|
||||
return;
|
||||
}
|
||||
if (wasAccepted && onEnd != null) {
|
||||
if (onEnd != null) {
|
||||
invokeCallback<void>('onEnd', () => onEnd(ForcePressDetails(
|
||||
pressure: 0.0,
|
||||
globalPosition: _lastPosition,
|
||||
)));
|
||||
}
|
||||
}
|
||||
_state = _ForceState.ready;
|
||||
}
|
||||
|
||||
@override
|
||||
void rejectGesture(int pointer) {
|
||||
stopTrackingPointer(pointer);
|
||||
didStopTrackingLastPointer(pointer);
|
||||
}
|
||||
|
||||
static double _inverseLerp(double min, double max, double t) {
|
||||
return (t - min) / (max - min);
|
||||
}
|
||||
|
||||
@override
|
||||
String get debugDescription => 'force press';
|
||||
}
|
|
@ -27,6 +27,10 @@ export 'package:flutter/gestures.dart' show
|
|||
GestureScaleStartCallback,
|
||||
GestureScaleUpdateCallback,
|
||||
GestureScaleEndCallback,
|
||||
GestureForcePressStartCallback,
|
||||
GestureForcePressPeakCallback,
|
||||
GestureForcePressEndCallback,
|
||||
GestureForcePressUpdateCallback,
|
||||
ScaleStartDetails,
|
||||
ScaleUpdateDetails,
|
||||
ScaleEndDetails,
|
||||
|
@ -167,6 +171,10 @@ class GestureDetector extends StatelessWidget {
|
|||
this.onHorizontalDragUpdate,
|
||||
this.onHorizontalDragEnd,
|
||||
this.onHorizontalDragCancel,
|
||||
this.onForcePressStart,
|
||||
this.onForcePressPeak,
|
||||
this.onForcePressUpdate,
|
||||
this.onForcePressEnd,
|
||||
this.onPanDown,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
|
@ -318,6 +326,37 @@ class GestureDetector extends StatelessWidget {
|
|||
/// The pointers are no longer in contact with the screen.
|
||||
final GestureScaleEndCallback onScaleEnd;
|
||||
|
||||
/// The pointer is in contact with the screen and has pressed with sufficient
|
||||
/// force to initiate a force press. The amount of force is at least
|
||||
/// [ForcePressGestureRecognizer.startPressure].
|
||||
///
|
||||
/// Note that this callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressStartCallback onForcePressStart;
|
||||
|
||||
/// The pointer is in contact with the screen and has pressed with the maximum
|
||||
/// force. The amount of force is at least
|
||||
/// [ForcePressGestureRecognizer.peakPressure].
|
||||
///
|
||||
/// Note that this callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressPeakCallback onForcePressPeak;
|
||||
|
||||
/// A pointer is in contact with the screen, has previously passed the
|
||||
/// [ForcePressGestureRecognizer.startPressure] and is either moving on the
|
||||
/// plane of the screen, pressing the screen with varying forces or both
|
||||
/// simultaneously.
|
||||
///
|
||||
/// Note that this callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressUpdateCallback onForcePressUpdate;
|
||||
|
||||
/// The pointer is no longer in contact with the screen.
|
||||
///
|
||||
/// Note that this callback will only be fired on devices with pressure
|
||||
/// detecting screens.
|
||||
final GestureForcePressEndCallback onForcePressEnd;
|
||||
|
||||
/// How this gesture detector should behave during hit testing.
|
||||
///
|
||||
/// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
|
||||
|
@ -435,6 +474,22 @@ class GestureDetector extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
if (onForcePressStart != null ||
|
||||
onForcePressPeak != null ||
|
||||
onForcePressUpdate != null ||
|
||||
onForcePressEnd != null) {
|
||||
gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
|
||||
() => ForcePressGestureRecognizer(debugOwner: this),
|
||||
(ForcePressGestureRecognizer instance) {
|
||||
instance
|
||||
..onStart = onForcePressStart
|
||||
..onPeak = onForcePressPeak
|
||||
..onUpdate = onForcePressUpdate
|
||||
..onEnd = onForcePressEnd;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return RawGestureDetector(
|
||||
gestures: gestures,
|
||||
behavior: behavior,
|
||||
|
|
452
packages/flutter/test/gestures/force_press_test.dart
Normal file
452
packages/flutter/test/gestures/force_press_test.dart
Normal file
|
@ -0,0 +1,452 @@
|
|||
// Copyright 2018 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 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'gesture_tester.dart';
|
||||
|
||||
void main() {
|
||||
setUp(ensureGestureBinding);
|
||||
|
||||
testGesture('A force press can be recognized', (GestureTester tester) {
|
||||
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
Offset startGlobalPosition;
|
||||
|
||||
void onStart(ForcePressDetails details) {
|
||||
startGlobalPosition = details.globalPosition;
|
||||
started += 1;
|
||||
}
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = onStart;
|
||||
force.onPeak = (ForcePressDetails details) => peaked += 1;
|
||||
force.onUpdate = (ForcePressDetails details) => updated += 1;
|
||||
force.onEnd = (ForcePressDetails details) => ended += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
tester.closeArena(pointerValue);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.5, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.8, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have just hit the start pressure so just the start event should be triggered and one update call should have occurred.
|
||||
expect(started, 1);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 1);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 5.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have exceeded the start pressure so update should be greater than 0.
|
||||
expect(started, 1);
|
||||
expect(updated, 5);
|
||||
expect(peaked, 0);
|
||||
expect(ended, 0);
|
||||
expect(startGlobalPosition, const Offset(10.0, 10.0));
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 6.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have exceeded the peak pressure so peak pressure should be true.
|
||||
expect(started, 1);
|
||||
expect(updated, 6);
|
||||
expect(peaked, 1);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 5.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// Update is still called.
|
||||
expect(started, 1);
|
||||
expect(updated, 10);
|
||||
expect(peaked, 1);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
// We have ended the gesture so ended should be true.
|
||||
expect(started, 1);
|
||||
expect(updated, 10);
|
||||
expect(peaked, 1);
|
||||
expect(ended, 1);
|
||||
});
|
||||
|
||||
testGesture('If minimum pressure is not reached, start and end callbacks are not called', (GestureTester tester) {
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = (_) => started += 1;
|
||||
force.onPeak = (_) => peaked += 1;
|
||||
force.onUpdate = (_) => updated += 1;
|
||||
force.onEnd = (_) => ended += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
tester.closeArena(1);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.5, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
});
|
||||
|
||||
testGesture('Should recognize drag and not force touch if there is a drag recognizer', (GestureTester tester) {
|
||||
final PanGestureRecognizer drag = PanGestureRecognizer();
|
||||
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = (_) => started += 1;
|
||||
force.onPeak = (_) => peaked += 1;
|
||||
force.onUpdate = (_) => updated += 1;
|
||||
force.onEnd = (_) => ended += 1;
|
||||
|
||||
int didStartPan = 0;
|
||||
drag.onStart = (_) => didStartPan += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
drag.addPointer(down);
|
||||
tester.closeArena(1);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 0);
|
||||
|
||||
tester.route(pointer.move(const Offset(30.0, 30.0))); // moved 20 horizontally and 20 vertically which is 28 total
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 1);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.5, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 1);
|
||||
|
||||
// We don't expect any events from the force press recognizer.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 1);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 1);
|
||||
});
|
||||
|
||||
testGesture('Should not call ended on pointer up if the gesture was never accepted', (GestureTester tester) {
|
||||
final PanGestureRecognizer drag = PanGestureRecognizer();
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = (_) => started += 1;
|
||||
force.onPeak = (_) => peaked += 1;
|
||||
force.onUpdate = (_) => updated += 1;
|
||||
force.onEnd = (_) => ended += 1;
|
||||
|
||||
int didStartPan = 0;
|
||||
drag.onStart = (_) => didStartPan += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
drag.addPointer(down);
|
||||
tester.closeArena(1);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 0);
|
||||
});
|
||||
|
||||
testGesture('Should call start only once if there is a competing gesture recognizer', (GestureTester tester) {
|
||||
final PanGestureRecognizer drag = PanGestureRecognizer();
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = (_) => started += 1;
|
||||
force.onPeak = (_) => peaked += 1;
|
||||
force.onUpdate = (_) => updated += 1;
|
||||
force.onEnd = (_) => ended += 1;
|
||||
|
||||
int didStartPan = 0;
|
||||
drag.onStart = (_) => didStartPan += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
drag.addPointer(down);
|
||||
tester.closeArena(1);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 0);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 1);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 1);
|
||||
expect(ended, 0);
|
||||
expect(didStartPan, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
expect(started, 1);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 1);
|
||||
expect(ended, 1);
|
||||
expect(didStartPan, 0);
|
||||
});
|
||||
|
||||
testGesture('A force press can be recognized with a custom interpolation function', (GestureTester tester) {
|
||||
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 6.66;
|
||||
|
||||
// Interpolated Flutter pressure values.
|
||||
const double startPressure = 0.4; // = Device pressure of 2.66.
|
||||
const double peakPressure = 0.85; // = Device pressure of 5.66.
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
|
||||
Offset startGlobalPosition;
|
||||
|
||||
void onStart(ForcePressDetails details) {
|
||||
startGlobalPosition = details.globalPosition;
|
||||
started += 1;
|
||||
}
|
||||
|
||||
double interpolateWithEasing(double min, double max, double t) {
|
||||
final double lerp = (t - min) / (max - min);
|
||||
return Curves.easeIn.transform(lerp);
|
||||
}
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure, interpolation: interpolateWithEasing);
|
||||
|
||||
force.onStart = onStart;
|
||||
force.onPeak = (ForcePressDetails details) => peaked += 1;
|
||||
force.onUpdate = (ForcePressDetails details) => updated += 1;
|
||||
force.onEnd = (ForcePressDetails details) => ended += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
tester.closeArena(pointerValue);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.5, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.8, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have just hit the start pressure so just the start event should be triggered and one update call should have occurred.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
expect(started, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
expect(started, 1);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 5.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have exceeded the start pressure so update should be greater than 0.
|
||||
expect(started, 1);
|
||||
expect(updated, 3);
|
||||
expect(peaked, 0);
|
||||
expect(ended, 0);
|
||||
expect(startGlobalPosition, const Offset(10.0, 10.0));
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 6.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// We have exceeded the peak pressure so peak pressure should be true.
|
||||
expect(started, 1);
|
||||
expect(updated, 4);
|
||||
expect(peaked, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 6.5, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
|
||||
|
||||
// Update is still called.
|
||||
expect(started, 1);
|
||||
expect(updated, 8);
|
||||
expect(peaked, 1);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
// We have ended the gesture so ended should be true.
|
||||
expect(started, 1);
|
||||
expect(updated, 8);
|
||||
expect(peaked, 1);
|
||||
expect(ended, 1);
|
||||
});
|
||||
}
|
|
@ -343,4 +343,146 @@ void main() {
|
|||
await longPress(kLongPressTimeout + const Duration(seconds: 1)); // To make sure the time for long press has occurred
|
||||
expect(longPressUp, 1);
|
||||
});
|
||||
|
||||
testWidgets('Force Press Callback called after force press', (WidgetTester tester) async {
|
||||
int forcePressStart = 0;
|
||||
int forcePressPeaked = 0;
|
||||
int forcePressUpdate = 0;
|
||||
int forcePressEnded = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onForcePressStart: (_) => forcePressStart += 1,
|
||||
onForcePressEnd: (_) => forcePressEnded += 1,
|
||||
onForcePressPeak: (_) => forcePressPeaked += 1,
|
||||
onForcePressUpdate: (_) => forcePressUpdate += 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0));
|
||||
const int pointerValue = 1;
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.3, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 0);
|
||||
expect(forcePressPeaked, 0);
|
||||
expect(forcePressUpdate, 0);
|
||||
expect(forcePressEnded, 0);
|
||||
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.5, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 1);
|
||||
expect(forcePressPeaked, 0);
|
||||
expect(forcePressUpdate, 1);
|
||||
expect(forcePressEnded, 0);
|
||||
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.6, pressureMin: 0, pressureMax: 1));
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.7, pressureMin: 0, pressureMax: 1));
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.2, pressureMin: 0, pressureMax: 1));
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.3, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 1);
|
||||
expect(forcePressPeaked, 0);
|
||||
expect(forcePressUpdate, 5);
|
||||
expect(forcePressEnded, 0);
|
||||
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.9, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 1);
|
||||
expect(forcePressPeaked, 1);
|
||||
expect(forcePressUpdate, 6);
|
||||
expect(forcePressEnded, 0);
|
||||
|
||||
await gesture.up();
|
||||
|
||||
expect(forcePressStart, 1);
|
||||
expect(forcePressPeaked, 1);
|
||||
expect(forcePressUpdate, 6);
|
||||
expect(forcePressEnded, 1);
|
||||
});
|
||||
|
||||
testWidgets('Force Press Callback not called if long press triggered before force press', (WidgetTester tester) async {
|
||||
int forcePressStart = 0;
|
||||
int longPressTimes = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onForcePressStart: (_) => forcePressStart += 1,
|
||||
onLongPress: () => longPressTimes += 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(400.0, 50.0));
|
||||
const int pointerValue = 1;
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(400.0, 50.0), pressure: 0.3, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 0);
|
||||
expect(longPressTimes, 0);
|
||||
|
||||
// Trigger the long press.
|
||||
await tester.pump(kLongPressTimeout + const Duration(seconds: 1));
|
||||
|
||||
expect(longPressTimes, 1);
|
||||
expect(forcePressStart, 0);
|
||||
|
||||
// Failed attempt to trigger the force press.
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(400.0, 50.0), pressure: 0.5, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(longPressTimes, 1);
|
||||
expect(forcePressStart, 0);
|
||||
});
|
||||
|
||||
testWidgets('Force Press Callback not called if drag triggered before force press', (WidgetTester tester) async {
|
||||
int forcePressStart = 0;
|
||||
int horizontalDragStart = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 100.0,
|
||||
color: const Color(0xFF00FF00),
|
||||
child: GestureDetector(
|
||||
onForcePressStart: (_) => forcePressStart += 1,
|
||||
onHorizontalDragStart: (_) => horizontalDragStart += 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
|
||||
const int pointerValue = 1;
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.3, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(forcePressStart, 0);
|
||||
expect(horizontalDragStart, 0);
|
||||
|
||||
// Trigger horizontal drag.
|
||||
await gesture.moveBy(const Offset(100, 0));
|
||||
|
||||
expect(horizontalDragStart, 1);
|
||||
expect(forcePressStart, 0);
|
||||
|
||||
// Failed attempt to trigger the force press.
|
||||
await gesture.updateWithCustomEvent(const PointerMoveEvent(pointer: pointerValue, position: Offset(0.0, 0.0), pressure: 0.5, pressureMin: 0, pressureMax: 1));
|
||||
|
||||
expect(horizontalDragStart, 1);
|
||||
expect(forcePressStart, 0);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,25 @@ class TestPointer {
|
|||
Offset get location => _location;
|
||||
Offset _location;
|
||||
|
||||
/// If a custom event is created outside of this class, this function is used
|
||||
/// to set the [isDown].
|
||||
bool setDownInfo(PointerEvent event, Offset newLocation) {
|
||||
_location = newLocation;
|
||||
switch (event.runtimeType) {
|
||||
case PointerDownEvent:
|
||||
assert(!isDown);
|
||||
_isDown = true;
|
||||
break;
|
||||
case PointerUpEvent:
|
||||
case PointerCancelEvent:
|
||||
assert(isDown);
|
||||
_isDown = false;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return isDown;
|
||||
}
|
||||
|
||||
/// Create a [PointerDownEvent] at the given location.
|
||||
///
|
||||
/// By default, the time stamp on the event is [Duration.zero]. You
|
||||
|
@ -157,10 +176,50 @@ class TestGesture {
|
|||
});
|
||||
}
|
||||
|
||||
/// Create a [TestGesture] by starting with a custom [PointerDownEvent] at the
|
||||
/// given point.
|
||||
///
|
||||
/// By default, the pointer identifier used is 1. This can be overridden by
|
||||
/// providing the `pointer` argument.
|
||||
///
|
||||
/// A function to use for hit testing should be provided via the `hitTester`
|
||||
/// argument, and a function to use for dispatching events should be provided
|
||||
/// via the `dispatcher` argument.
|
||||
static Future<TestGesture> downWithCustomEvent(Offset downLocation, PointerDownEvent downEvent, {
|
||||
int pointer = 1,
|
||||
@required HitTester hitTester,
|
||||
@required EventDispatcher dispatcher,
|
||||
}) async {
|
||||
assert(hitTester != null);
|
||||
assert(dispatcher != null);
|
||||
TestGesture result;
|
||||
return TestAsyncUtils.guard<void>(() async {
|
||||
// dispatch down event
|
||||
final HitTestResult hitTestResult = hitTester(downLocation);
|
||||
final TestPointer testPointer = TestPointer(pointer);
|
||||
testPointer.setDownInfo(downEvent, downLocation);
|
||||
await dispatcher(downEvent, hitTestResult);
|
||||
// create a TestGesture
|
||||
result = TestGesture._(dispatcher, hitTestResult, testPointer);
|
||||
}).then<TestGesture>((void value) {
|
||||
return result;
|
||||
}, onError: (dynamic error, StackTrace stack) {
|
||||
return Future<TestGesture>.error(error, stack);
|
||||
});
|
||||
}
|
||||
|
||||
final EventDispatcher _dispatcher;
|
||||
final HitTestResult _result;
|
||||
final TestPointer _pointer;
|
||||
|
||||
/// Send a move event moving the pointer by the given offset.
|
||||
Future<void> updateWithCustomEvent(PointerEvent event, { Duration timeStamp = Duration.zero }) {
|
||||
_pointer.setDownInfo(event, event.position);
|
||||
return TestAsyncUtils.guard<void>(() {
|
||||
return _dispatcher(event, _result);
|
||||
});
|
||||
}
|
||||
|
||||
/// Send a move event moving the pointer by the given offset.
|
||||
Future<void> moveBy(Offset offset, { Duration timeStamp = Duration.zero }) {
|
||||
assert(_pointer._isDown);
|
||||
|
|
Loading…
Reference in a new issue