Add position data to OnDragEnd callback (#140378)

This PR adds localPosition/globalPosition data to the `DragEndDetails`
argument of the `onDragEnd` callback.
This commit is contained in:
Henry Riehl 2024-02-09 20:37:20 +00:00 committed by GitHub
parent ed02cf2f4a
commit fa71e8029b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 72 additions and 3 deletions

View file

@ -214,11 +214,14 @@ class DragEndDetails {
DragEndDetails({
this.velocity = Velocity.zero,
this.primaryVelocity,
this.globalPosition = Offset.zero,
Offset? localPosition,
}) : assert(
primaryVelocity == null
|| (primaryVelocity == velocity.pixelsPerSecond.dx && velocity.pixelsPerSecond.dy == 0)
|| (primaryVelocity == velocity.pixelsPerSecond.dy && velocity.pixelsPerSecond.dx == 0),
);
),
localPosition = localPosition ?? globalPosition;
/// The velocity the pointer was moving when it stopped contacting the screen.
///
@ -237,6 +240,23 @@ class DragEndDetails {
/// Defaults to null if not specified in the constructor.
final double? primaryVelocity;
/// The global position the pointer is located at when the drag
/// gesture has been completed.
///
/// Defaults to the origin if not specified in the constructor.
///
/// See also:
///
/// * [localPosition], which is the [globalPosition] transformed to the
/// coordinate space of the event receiver.
final Offset globalPosition;
/// The local position in the coordinate system of the event receiver when
/// the drag gesture has been completed.
///
/// Defaults to [globalPosition] if not specified in the constructor.
final Offset localPosition;
@override
String toString() => '${objectRuntimeType(this, 'DragEndDetails')}($velocity)';
}

View file

@ -276,6 +276,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
_DragState _state = _DragState.ready;
late OffsetPair _initialPosition;
late OffsetPair _pendingDragOffset;
late OffsetPair _finalPosition;
Duration? _lastPendingEventTimestamp;
/// When asserts are enabled, returns the last tracked pending event timestamp
@ -351,6 +352,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
if (_state == _DragState.ready) {
_state = _DragState.possible;
_initialPosition = OffsetPair(global: event.position, local: event.localPosition);
_finalPosition = _initialPosition;
_pendingDragOffset = OffsetPair.zero;
_globalDistanceMoved = 0.0;
_lastPendingEventTimestamp = event.timeStamp;
@ -418,6 +420,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
final Offset localDelta = (event is PointerMoveEvent) ? event.localDelta : (event as PointerPanZoomUpdateEvent).localPanDelta;
final Offset position = (event is PointerMoveEvent) ? event.position : (event.position + (event as PointerPanZoomUpdateEvent).pan);
final Offset localPosition = (event is PointerMoveEvent) ? event.localPosition : (event.localPosition + (event as PointerPanZoomUpdateEvent).localPan);
_finalPosition = OffsetPair(local: localPosition, global: position);
if (_state == _DragState.accepted) {
_checkUpdate(
sourceTimeStamp: event.timeStamp,
@ -600,7 +603,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
? () => '$estimate; fling at ${details!.velocity}.'
: () => '$estimate; judged to not be a fling.';
}
details ??= DragEndDetails(primaryVelocity: 0.0);
details ??= DragEndDetails(
primaryVelocity: 0.0,
globalPosition: _finalPosition.global,
localPosition: _finalPosition.local,
);
invokeCallback<void>('onEnd', () => onEnd!(details!), debugReport: debugReport);
}
@ -660,6 +667,8 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
return DragEndDetails(
velocity: Velocity(pixelsPerSecond: Offset(0, dy)),
primaryVelocity: dy,
globalPosition: _finalPosition.global,
localPosition: _finalPosition.local,
);
}
@ -715,6 +724,8 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
return DragEndDetails(
velocity: Velocity(pixelsPerSecond: Offset(dx, 0)),
primaryVelocity: dx,
globalPosition: _finalPosition.global,
localPosition: _finalPosition.local,
);
}
@ -765,7 +776,11 @@ class PanGestureRecognizer extends DragGestureRecognizer {
}
final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
.clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
return DragEndDetails(velocity: velocity);
return DragEndDetails(
velocity: velocity,
globalPosition: _finalPosition.global,
localPosition: _finalPosition.local,
);
}
@override

View file

@ -125,6 +125,40 @@ void main() {
expect(didEndPan, isTrue);
});
testWidgets('DragEndDetails returns the last known position', (WidgetTester tester) async {
Offset updateOffset = const Offset(10.0, 10.0);
const EdgeInsets paddingOffset = EdgeInsets.all(10.0);
Offset? endOffset;
Offset? globalEndOffset;
await tester.pumpWidget(
Padding(
padding: paddingOffset,
child: GestureDetector(
onPanStart: (DragStartDetails details) {
},
onPanUpdate: (DragUpdateDetails details) {
updateOffset += details.delta;
},
onPanEnd: (DragEndDetails details) {
endOffset = details.localPosition;
globalEndOffset = details.globalPosition;
},
child: Container(
color: const Color(0xFF00FF00),
),
),
),
);
await tester.dragFrom(const Offset(20.0, 20.0), const Offset(30.0, 40.0));
expect(endOffset, isNotNull);
expect(updateOffset, endOffset);
// Make sure details.globalPosition works correctly.
expect(Offset(endOffset!.dx + paddingOffset.left, endOffset!.dy + paddingOffset.top), globalEndOffset);
});
group('Tap', () {
final ButtonVariant buttonVariant = ButtonVariant(
values: <int>[kPrimaryButton, kSecondaryButton, kTertiaryButton],