Lazily compute PointerEvent's transformed positions (#63813)

* PointerEvent.local* properties are calculated lazily; other properties are delegated to original.
* Transformed PointerEvent becomes a subclass of its original class.
* Unnamed constructors no longer accepts transform and origin.
diff --git a/packages/flutter/lib/src/gestures/events.dart b/packages/flutter/lib/src/gestures/events.dart
index f96d4a5..0a1a589 100644
--- a/packages/flutter/lib/src/gestures/events.dart
+++ b/packages/flutter/lib/src/gestures/events.dart
@@ -243,9 +243,7 @@
     this.kind = PointerDeviceKind.touch,
     this.device = 0,
     this.position = Offset.zero,
-    Offset? localPosition,
     this.delta = Offset.zero,
-    Offset? localDelta,
     this.buttons = 0,
     this.down = false,
     this.obscured = false,
@@ -265,8 +263,7 @@
     this.synthesized = false,
     this.transform,
     this.original,
-  }) : localPosition = localPosition ?? position,
-       localDelta = localDelta ?? delta;
+  });
 
   /// Unique identifier that ties the [PointerEvent] to the embedder event that created it.
   ///
@@ -307,7 +304,7 @@
   ///
   ///  * [position], which is the position in the global coordinate system of
   ///    the screen.
-  final Offset localPosition;
+  Offset get localPosition => position;
 
   /// Distance in logical pixels that the pointer moved since the last
   /// [PointerMoveEvent] or [PointerHoverEvent].
@@ -329,7 +326,7 @@
   ///
   ///  * [delta], which is the distance the pointer moved in the global
   ///    coordinate system of the screen.
-  final Offset localDelta;
+  Offset get localDelta => delta;
 
   /// Bit field using the *Button constants such as [kPrimaryMouseButton],
   /// [kSecondaryStylusButton], etc.
@@ -511,24 +508,32 @@
   /// The coordinate space of the event receiver is described by `transform`. A
   /// null value for `transform` is treated as the identity transformation.
   ///
-  /// The method may return the same object instance if for example the
-  /// transformation has no effect on the event.
+  /// The resulting event will store the base event as [original], delegates
+  /// most properties to [original], except for [localPosition] and [localDelta],
+  /// which are calculated based on [transform] on first use and cached.
   ///
-  /// Transforms are not commutative. If this method is called on a
-  /// [PointerEvent] that has a non-null [transform] value, that value will be
-  /// overridden by the provided `transform`.
+  /// The method may return the same object instance if for example the
+  /// transformation has no effect on the event. Otherwise, the resulting event
+  /// will be a subclass of, but not exactly, the original event class (e.g.
+  /// [PointerDownEvent.transformed] may return a subclass of [PointerDownEvent]).
+  ///
+  /// Transforms are not commutative, and are based on [original] events.
+  /// If this method is called on a transformed event, the provided `transform`
+  /// will override (instead of multiplied onto) the existing [transform] and
+  /// used to calculate the new [localPosition] and [localDelta].
   PointerEvent transformed(Matrix4? transform);
 
   /// Creates a copy of event with the specified properties replaced.
+  ///
+  /// Calling this method on a transformed event will return a new transformed
+  /// event based on the current [transform] and the provided properties.
   PointerEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -544,48 +549,9 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   });
 
-  @override
-  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
-    super.debugFillProperties(properties);
-    properties.add(DiagnosticsProperty<Offset>('position', position));
-    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition, defaultValue: position, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<Offset>('delta', delta, defaultValue: Offset.zero, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<Offset>('localDelta', localDelta, defaultValue: delta, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp, defaultValue: Duration.zero, level: DiagnosticLevel.debug));
-    properties.add(IntProperty('pointer', pointer, level: DiagnosticLevel.debug));
-    properties.add(EnumProperty<PointerDeviceKind>('kind', kind, level: DiagnosticLevel.debug));
-    properties.add(IntProperty('device', device, defaultValue: 0, level: DiagnosticLevel.debug));
-    properties.add(IntProperty('buttons', buttons, defaultValue: 0, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<bool>('down', down, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('pressure', pressure, defaultValue: 1.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('pressureMin', pressureMin, defaultValue: 1.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('pressureMax', pressureMax, defaultValue: 1.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('distance', distance, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('distanceMin', distanceMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('distanceMax', distanceMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('size', size, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('radiusMajor', radiusMajor, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('radiusMinor', radiusMinor, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('radiusMin', radiusMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('radiusMax', radiusMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('orientation', orientation, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(DoubleProperty('tilt', tilt, defaultValue: 0.0, level: DiagnosticLevel.debug));
-    properties.add(IntProperty('platformData', platformData, defaultValue: 0, level: DiagnosticLevel.debug));
-    properties.add(FlagProperty('obscured', value: obscured, ifTrue: 'obscured', level: DiagnosticLevel.debug));
-    properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized', level: DiagnosticLevel.debug));
-    properties.add(IntProperty('embedderId', embedderId, defaultValue: 0, level: DiagnosticLevel.debug));
-  }
-
-  /// Returns a complete textual description of this event.
-  String toStringFull() {
-    return toString(minLevel: DiagnosticLevel.fine);
-  }
-
   /// Returns the transformation of `position` into the coordinate system
   /// described by `transform`.
   ///
@@ -642,20 +608,209 @@
   }
 }
 
+// A mixin that adds implementation for [debugFillProperties] and [toStringFull]
+// to [PointerEvent].
+mixin _PointerEventDescription on PointerEvent {
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('position', position));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition, defaultValue: position, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Offset>('delta', delta, defaultValue: Offset.zero, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Offset>('localDelta', localDelta, defaultValue: delta, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp, defaultValue: Duration.zero, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('pointer', pointer, level: DiagnosticLevel.debug));
+    properties.add(EnumProperty<PointerDeviceKind>('kind', kind, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('device', device, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('buttons', buttons, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<bool>('down', down, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressure', pressure, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressureMin', pressureMin, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressureMax', pressureMax, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distance', distance, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distanceMin', distanceMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distanceMax', distanceMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('size', size, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMajor', radiusMajor, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMinor', radiusMinor, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMin', radiusMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMax', radiusMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('orientation', orientation, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('tilt', tilt, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('platformData', platformData, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(FlagProperty('obscured', value: obscured, ifTrue: 'obscured', level: DiagnosticLevel.debug));
+    properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized', level: DiagnosticLevel.debug));
+    properties.add(IntProperty('embedderId', embedderId, defaultValue: 0, level: DiagnosticLevel.debug));
+  }
+
+  /// Returns a complete textual description of this event.
+  String toStringFull() {
+    return toString(minLevel: DiagnosticLevel.fine);
+  }
+}
+
+abstract class _AbstractPointerEvent implements PointerEvent {}
+
+// The base class for transformed pointer event classes.
+//
+// A _TransformedPointerEvent stores an [original] event and the [transform]
+// matrix. It defers all field getters to the original event, except for
+// [localPosition] and [localDelta], which are calculated when first used.
+abstract class _TransformedPointerEvent extends _AbstractPointerEvent with Diagnosticable, _PointerEventDescription {
+
+  @override
+  PointerEvent get original;
+
+  @override
+  Matrix4 get transform;
+
+  @override
+  int get embedderId => original.embedderId;
+
+  @override
+  Duration get timeStamp => original.timeStamp;
+
+  @override
+  int get pointer => original.pointer;
+
+  @override
+  PointerDeviceKind get kind => original.kind;
+
+  @override
+  int get device => original.device;
+
+  @override
+  Offset get position => original.position;
+
+  @override
+  Offset get delta => original.delta;
+
+  @override
+  int get buttons => original.buttons;
+
+  @override
+  bool get down => original.down;
+
+  @override
+  bool get obscured => original.obscured;
+
+  @override
+  double get pressure => original.pressure;
+
+  @override
+  double get pressureMin => original.pressureMin;
+
+  @override
+  double get pressureMax => original.pressureMax;
+
+  @override
+  double get distance => original.distance;
+
+  @override
+  double get distanceMin => 0.0;
+
+  @override
+  double get distanceMax => original.distanceMax;
+
+  @override
+  double get size => original.size;
+
+  @override
+  double get radiusMajor => original.radiusMajor;
+
+  @override
+  double get radiusMinor => original.radiusMinor;
+
+  @override
+  double get radiusMin => original.radiusMin;
+
+  @override
+  double get radiusMax => original.radiusMax;
+
+  @override
+  double get orientation => original.orientation;
+
+  @override
+  double get tilt => original.tilt;
+
+  @override
+  int get platformData => original.platformData;
+
+  @override
+  bool get synthesized => original.synthesized;
+
+  @override
+  late final Offset localPosition = PointerEvent.transformPosition(transform, position);
+
+  @override
+  late final Offset localDelta = PointerEvent.transformDeltaViaPositions(
+    transform: transform,
+    untransformedDelta: delta,
+    untransformedEndPosition: position,
+    transformedEndPosition: localPosition,
+  );
+}
+
+mixin _CopyPointerAddedEvent on PointerEvent {
+  @override
+  PointerAddedEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerAddedEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
 /// The device has started tracking the pointer.
 ///
 /// For example, the pointer might be hovering above the device, having not yet
 /// made contact with the surface of the device.
-class PointerAddedEvent extends PointerEvent {
+class PointerAddedEvent extends PointerEvent with _PointerEventDescription, _CopyPointerAddedEvent {
   /// Creates a pointer added event.
   ///
   /// All of the arguments must be non-null.
   const PointerAddedEvent({
     Duration timeStamp = Duration.zero,
+    int pointer = 0,
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     bool obscured = false,
     double pressureMin = 1.0,
     double pressureMax = 1.0,
@@ -665,15 +820,13 @@
     double radiusMax = 0.0,
     double orientation = 0.0,
     double tilt = 0.0,
-    Matrix4? transform,
-    PointerAddedEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
+         pointer: pointer,
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          obscured: obscured,
          pressure: 0.0,
          pressureMin: pressureMin,
@@ -684,8 +837,6 @@
          radiusMax: radiusMax,
          orientation: orientation,
          tilt: tilt,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -694,37 +845,33 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    return PointerAddedEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      transform: transform,
-      original: original as PointerAddedEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerAddedEvent(original as PointerAddedEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerAddedEvent extends _TransformedPointerEvent with _CopyPointerAddedEvent implements PointerAddedEvent {
+  _TransformedPointerAddedEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerAddedEvent copyWith({
+  final PointerAddedEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerAddedEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerRemovedEvent on PointerEvent {
+  @override
+  PointerRemovedEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -740,29 +887,21 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerAddedEvent(
+    return PointerRemovedEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
       obscured: obscured ?? this.obscured,
       pressureMin: pressureMin ?? this.pressureMin,
       pressureMax: pressureMax ?? this.pressureMax,
-      distance: distance ?? this.distance,
       distanceMax: distanceMax ?? this.distanceMax,
       radiusMin: radiusMin ?? this.radiusMin,
       radiusMax: radiusMax ?? this.radiusMax,
-      orientation: orientation ?? this.orientation,
-      tilt: tilt ?? this.tilt,
-      transform: transform ?? this.transform,
-      original: original as PointerAddedEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -770,31 +909,30 @@
 ///
 /// For example, the pointer might have drifted out of the device's hover
 /// detection range or might have been disconnected from the system entirely.
-class PointerRemovedEvent extends PointerEvent {
+class PointerRemovedEvent extends PointerEvent with _PointerEventDescription, _CopyPointerRemovedEvent {
   /// Creates a pointer removed event.
   ///
   /// All of the arguments must be non-null.
   const PointerRemovedEvent({
     Duration timeStamp = Duration.zero,
+    int pointer = 0,
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     bool obscured = false,
     double pressureMin = 1.0,
     double pressureMax = 1.0,
     double distanceMax = 0.0,
     double radiusMin = 0.0,
     double radiusMax = 0.0,
-    Matrix4? transform,
     PointerRemovedEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
+         pointer: pointer,
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          obscured: obscured,
          pressure: 0.0,
          pressureMin: pressureMin,
@@ -802,7 +940,6 @@
          distanceMax: distanceMax,
          radiusMin: radiusMin,
          radiusMax: radiusMax,
-         transform: transform,
          original: original,
          embedderId: embedderId,
        );
@@ -812,34 +949,33 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    return PointerRemovedEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distanceMax: distanceMax,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      transform: transform,
-      original: original as PointerRemovedEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerRemovedEvent(original as PointerRemovedEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerRemovedEvent extends _TransformedPointerEvent with _CopyPointerRemovedEvent implements PointerRemovedEvent {
+  _TransformedPointerRemovedEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerRemovedEvent copyWith({
+  final PointerRemovedEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerRemovedEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerHoverEvent on PointerEvent {
+  @override
+  PointerHoverEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -855,26 +991,30 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerRemovedEvent(
+    return PointerHoverEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
+      delta: delta ?? this.delta,
+      buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
       pressureMin: pressureMin ?? this.pressureMin,
       pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
       distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
       radiusMin: radiusMin ?? this.radiusMin,
       radiusMax: radiusMax ?? this.radiusMax,
-      transform: transform ?? this.transform,
-      original: original as PointerRemovedEvent? ?? this,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      synthesized: synthesized ?? this.synthesized,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -890,18 +1030,17 @@
 ///    contact with the device.
 ///  * [Listener.onPointerHover], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerHoverEvent extends PointerEvent {
+class PointerHoverEvent extends PointerEvent with _PointerEventDescription, _CopyPointerHoverEvent {
   /// Creates a pointer hover event.
   ///
   /// All of the arguments must be non-null.
   const PointerHoverEvent({
     Duration timeStamp = Duration.zero,
     PointerDeviceKind kind = PointerDeviceKind.touch,
+    int pointer = 0,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     Offset delta = Offset.zero,
-    Offset? localDelta,
     int buttons = 0,
     bool obscured = false,
     double pressureMin = 1.0,
@@ -916,17 +1055,14 @@
     double orientation = 0.0,
     double tilt = 0.0,
     bool synthesized = false,
-    Matrix4? transform,
-    PointerHoverEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
+         pointer: pointer,
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          delta: delta,
-         localDelta: localDelta,
          buttons: buttons,
          down: false,
          obscured: obscured,
@@ -943,8 +1079,6 @@
          orientation: orientation,
          tilt: tilt,
          synthesized: synthesized,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -953,50 +1087,33 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
-    return PointerHoverEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: transformedPosition,
-      delta: delta,
-      localDelta: PointerEvent.transformDeltaViaPositions(
-        transform: transform,
-        untransformedDelta: delta,
-        untransformedEndPosition: position,
-        transformedEndPosition: transformedPosition,
-      ),
-      buttons: buttons,
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      synthesized: synthesized,
-      transform: transform,
-      original: original as PointerHoverEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerHoverEvent(original as PointerHoverEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerHoverEvent extends _TransformedPointerEvent with _CopyPointerHoverEvent implements PointerHoverEvent {
+  _TransformedPointerHoverEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerHoverEvent copyWith({
+  final PointerHoverEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerHoverEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerEnterEvent on PointerEvent {
+  @override
+  PointerEnterEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -1012,18 +1129,14 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerHoverEvent(
+    return PointerEnterEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
       delta: delta ?? this.delta,
-      localDelta: localDelta ?? this.localDelta,
       buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
       pressureMin: pressureMin ?? this.pressureMin,
@@ -1038,10 +1151,8 @@
       orientation: orientation ?? this.orientation,
       tilt: tilt ?? this.tilt,
       synthesized: synthesized ?? this.synthesized,
-      transform: transform ?? this.transform,
-      original: original as PointerHoverEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -1057,18 +1168,17 @@
 ///    contact with the device.
 ///  * [MouseRegion.onEnter], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerEnterEvent extends PointerEvent {
+class PointerEnterEvent extends PointerEvent with _PointerEventDescription, _CopyPointerEnterEvent {
   /// Creates a pointer enter event.
   ///
   /// All of the arguments must be non-null.
   const PointerEnterEvent({
     Duration timeStamp = Duration.zero,
+    int pointer = 0,
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     Offset delta = Offset.zero,
-    Offset? localDelta,
     int buttons = 0,
     bool obscured = false,
     double pressureMin = 1.0,
@@ -1084,17 +1194,14 @@
     double tilt = 0.0,
     bool down = false,
     bool synthesized = false,
-    Matrix4? transform,
-    PointerEnterEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
+         pointer: pointer,
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          delta: delta,
-         localDelta: localDelta,
          buttons: buttons,
          down: down,
          obscured: obscured,
@@ -1111,8 +1218,6 @@
          orientation: orientation,
          tilt: tilt,
          synthesized: synthesized,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -1123,19 +1228,18 @@
     'Use PointerEnterEvent.fromMouseEvent instead. '
     'This feature was deprecated after v1.4.3.'
   )
-  PointerEnterEvent.fromHoverEvent(PointerHoverEvent event) : this.fromMouseEvent(event);
+  factory PointerEnterEvent.fromHoverEvent(PointerHoverEvent event) => PointerEnterEvent.fromMouseEvent(event);
 
   /// Creates an enter event from a [PointerEvent].
   ///
   /// This is used by the [MouseTracker] to synthesize enter events.
-  PointerEnterEvent.fromMouseEvent(PointerEvent event) : this(
+  factory PointerEnterEvent.fromMouseEvent(PointerEvent event) => PointerEnterEvent(
     timeStamp: event.timeStamp,
+    pointer: event.pointer,
     kind: event.kind,
     device: event.device,
     position: event.position,
-    localPosition: event.localPosition,
     delta: event.delta,
-    localDelta: event.localDelta,
     buttons: event.buttons,
     obscured: event.obscured,
     pressureMin: event.pressureMin,
@@ -1151,60 +1255,40 @@
     tilt: event.tilt,
     down: event.down,
     synthesized: event.synthesized,
-    transform: event.transform,
-    original: null,
-  );
+  ).transformed(event.transform);
 
   @override
   PointerEnterEvent transformed(Matrix4? transform) {
     if (transform == null || transform == this.transform) {
       return this;
     }
-    final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
-    return PointerEnterEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: transformedPosition,
-      delta: delta,
-      localDelta: PointerEvent.transformDeltaViaPositions(
-        transform: transform,
-        untransformedDelta: delta,
-        untransformedEndPosition: position,
-        transformedEndPosition: transformedPosition,
-      ),
-      buttons: buttons,
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      down: down,
-      synthesized: synthesized,
-      transform: transform,
-      original: original as PointerEnterEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerEnterEvent(original as PointerEnterEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerEnterEvent extends _TransformedPointerEvent with _CopyPointerEnterEvent implements PointerEnterEvent {
+  _TransformedPointerEnterEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerEnterEvent copyWith({
+  final PointerEnterEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerEnterEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerExitEvent on PointerEvent {
+  @override
+  PointerExitEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -1220,18 +1304,14 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerEnterEvent(
+    return PointerExitEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
       delta: delta ?? this.delta,
-      localDelta: localDelta ?? this.localDelta,
       buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
       pressureMin: pressureMin ?? this.pressureMin,
@@ -1246,10 +1326,8 @@
       orientation: orientation ?? this.orientation,
       tilt: tilt ?? this.tilt,
       synthesized: synthesized ?? this.synthesized,
-      transform: transform ?? this.transform,
-      original: original as PointerEnterEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -1265,18 +1343,17 @@
 ///    contact with the device.
 ///  * [MouseRegion.onExit], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerExitEvent extends PointerEvent {
+class PointerExitEvent extends PointerEvent with _PointerEventDescription, _CopyPointerExitEvent {
   /// Creates a pointer exit event.
   ///
   /// All of the arguments must be non-null.
   const PointerExitEvent({
     Duration timeStamp = Duration.zero,
     PointerDeviceKind kind = PointerDeviceKind.touch,
+    int pointer = 0,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     Offset delta = Offset.zero,
-    Offset? localDelta,
     int buttons = 0,
     bool obscured = false,
     double pressureMin = 1.0,
@@ -1292,17 +1369,14 @@
     double tilt = 0.0,
     bool down = false,
     bool synthesized = false,
-    Matrix4? transform,
-    PointerExitEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
+         pointer: pointer,
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          delta: delta,
-         localDelta: localDelta,
          buttons: buttons,
          down: down,
          obscured: obscured,
@@ -1319,31 +1393,28 @@
          orientation: orientation,
          tilt: tilt,
          synthesized: synthesized,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
-  /// Creates an exit event from a [PointerHoverEvent].
+  /// Creates an enter event from a [PointerHoverEvent].
   ///
   /// Deprecated. Please use [PointerExitEvent.fromMouseEvent] instead.
   @Deprecated(
     'Use PointerExitEvent.fromMouseEvent instead. '
     'This feature was deprecated after v1.4.3.'
   )
-  PointerExitEvent.fromHoverEvent(PointerHoverEvent event) : this.fromMouseEvent(event);
+  factory PointerExitEvent.fromHoverEvent(PointerHoverEvent event) => PointerExitEvent.fromMouseEvent(event);
 
   /// Creates an exit event from a [PointerEvent].
   ///
   /// This is used by the [MouseTracker] to synthesize exit events.
-  PointerExitEvent.fromMouseEvent(PointerEvent event) : this(
+  factory PointerExitEvent.fromMouseEvent(PointerEvent event) => PointerExitEvent(
     timeStamp: event.timeStamp,
+    pointer: event.pointer,
     kind: event.kind,
     device: event.device,
     position: event.position,
-    localPosition: event.localPosition,
     delta: event.delta,
-    localDelta: event.localDelta,
     buttons: event.buttons,
     obscured: event.obscured,
     pressureMin: event.pressureMin,
@@ -1359,60 +1430,41 @@
     tilt: event.tilt,
     down: event.down,
     synthesized: event.synthesized,
-    transform: event.transform,
-    original: null,
-  );
+  ).transformed(event.transform);
 
   @override
   PointerExitEvent transformed(Matrix4? transform) {
     if (transform == null || transform == this.transform) {
       return this;
     }
-    final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
-    return PointerExitEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: transformedPosition,
-      delta: delta,
-      localDelta: PointerEvent.transformDeltaViaPositions(
-        transform: transform,
-        untransformedDelta: delta,
-        untransformedEndPosition: position,
-        transformedEndPosition: transformedPosition,
-      ),
-      buttons: buttons,
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      down: down,
-      synthesized: synthesized,
-      transform: transform,
-      original: original as PointerExitEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerExitEvent(original as PointerExitEvent? ?? this, transform);
   }
 
+}
+
+class _TransformedPointerExitEvent extends _TransformedPointerEvent with _CopyPointerExitEvent implements PointerExitEvent {
+  _TransformedPointerExitEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
   @override
-  PointerExitEvent copyWith({
+  final PointerExitEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerExitEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerDownEvent on PointerEvent {
+  @override
+  PointerDownEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -1428,23 +1480,19 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerExitEvent(
+    return PointerDownEvent(
       timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
-      delta: delta ?? this.delta,
-      localDelta: localDelta ?? this.localDelta,
       buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
+      pressure: pressure ?? this.pressure,
       pressureMin: pressureMin ?? this.pressureMin,
       pressureMax: pressureMax ?? this.pressureMax,
-      distance: distance ?? this.distance,
       distanceMax: distanceMax ?? this.distanceMax,
       size: size ?? this.size,
       radiusMajor: radiusMajor ?? this.radiusMajor,
@@ -1453,11 +1501,8 @@
       radiusMax: radiusMax ?? this.radiusMax,
       orientation: orientation ?? this.orientation,
       tilt: tilt ?? this.tilt,
-      synthesized: synthesized ?? this.synthesized,
-      transform: transform ?? this.transform,
-      original: original as PointerExitEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -1467,7 +1512,7 @@
 ///
 ///  * [Listener.onPointerDown], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerDownEvent extends PointerEvent {
+class PointerDownEvent extends PointerEvent with _PointerEventDescription, _CopyPointerDownEvent {
   /// Creates a pointer down event.
   ///
   /// All of the arguments must be non-null.
@@ -1477,7 +1522,6 @@
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     int buttons = kPrimaryButton,
     bool obscured = false,
     double pressure = 1.0,
@@ -1491,8 +1535,6 @@
     double radiusMax = 0.0,
     double orientation = 0.0,
     double tilt = 0.0,
-    Matrix4? transform,
-    PointerDownEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
@@ -1500,7 +1542,6 @@
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          buttons: buttons,
          down: true,
          obscured: obscured,
@@ -1516,8 +1557,6 @@
          radiusMax: radiusMax,
          orientation: orientation,
          tilt: tilt,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -1526,42 +1565,33 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    return PointerDownEvent(
-      timeStamp: timeStamp,
-      pointer: pointer,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      buttons: buttons,
-      obscured: obscured,
-      pressure: pressure,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      transform: transform,
-      original: original as PointerDownEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerDownEvent(original as PointerDownEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerDownEvent extends _TransformedPointerEvent with _CopyPointerDownEvent implements PointerDownEvent {
+  _TransformedPointerDownEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerDownEvent copyWith({
+  final PointerDownEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerDownEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerMoveEvent on PointerEvent {
+  @override
+  PointerMoveEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -1577,17 +1607,15 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerDownEvent(
+    return PointerMoveEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       pointer: pointer ?? this.pointer,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
+      delta: delta ?? this.delta,
       buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
       pressure: pressure ?? this.pressure,
@@ -1601,10 +1629,9 @@
       radiusMax: radiusMax ?? this.radiusMax,
       orientation: orientation ?? this.orientation,
       tilt: tilt ?? this.tilt,
-      transform: transform ?? this.transform,
-      original: original as PointerDownEvent? ?? this,
+      synthesized: synthesized ?? this.synthesized,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -1617,7 +1644,7 @@
 ///    contact with the device.
 ///  * [Listener.onPointerMove], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerMoveEvent extends PointerEvent {
+class PointerMoveEvent extends PointerEvent with _PointerEventDescription, _CopyPointerMoveEvent {
   /// Creates a pointer move event.
   ///
   /// All of the arguments must be non-null.
@@ -1627,9 +1654,7 @@
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     Offset delta = Offset.zero,
-    Offset? localDelta,
     int buttons = kPrimaryButton,
     bool obscured = false,
     double pressure = 1.0,
@@ -1645,8 +1670,6 @@
     double tilt = 0.0,
     int platformData = 0,
     bool synthesized = false,
-    Matrix4? transform,
-    PointerMoveEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
@@ -1654,9 +1677,7 @@
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          delta: delta,
-         localDelta: localDelta,
          buttons: buttons,
          down: true,
          obscured: obscured,
@@ -1674,8 +1695,6 @@
          tilt: tilt,
          platformData: platformData,
          synthesized: synthesized,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -1684,45 +1703,28 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    final Offset transformedPosition = PointerEvent.transformPosition(transform, position);
 
-    return PointerMoveEvent(
-      timeStamp: timeStamp,
-      pointer: pointer,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: transformedPosition,
-      delta: delta,
-      localDelta: PointerEvent.transformDeltaViaPositions(
-        transform: transform,
-        untransformedDelta: delta,
-        untransformedEndPosition: position,
-        transformedEndPosition: transformedPosition,
-      ),
-      buttons: buttons,
-      obscured: obscured,
-      pressure: pressure,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      platformData: platformData,
-      synthesized: synthesized,
-      transform: transform,
-      original: original as PointerMoveEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerMoveEvent(original as PointerMoveEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerMoveEvent extends _TransformedPointerEvent with _CopyPointerMoveEvent implements PointerMoveEvent {
+  _TransformedPointerMoveEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerMoveEvent copyWith({
+  final PointerMoveEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerMoveEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerUpEvent on PointerEvent {
+  @override
+  PointerUpEvent copyWith({
     Duration? timeStamp,
     int? pointer,
     PointerDeviceKind? kind,
@@ -1730,7 +1732,6 @@
     Offset? position,
     Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -1746,24 +1747,20 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
-    return PointerMoveEvent(
+    return PointerUpEvent(
       timeStamp: timeStamp ?? this.timeStamp,
       pointer: pointer ?? this.pointer,
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
-      delta: delta ?? this.delta,
-      localDelta: localDelta ?? this.localDelta,
       buttons: buttons ?? this.buttons,
       obscured: obscured ?? this.obscured,
       pressure: pressure ?? this.pressure,
       pressureMin: pressureMin ?? this.pressureMin,
       pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
       distanceMax: distanceMax ?? this.distanceMax,
       size: size ?? this.size,
       radiusMajor: radiusMajor ?? this.radiusMajor,
@@ -1772,11 +1769,8 @@
       radiusMax: radiusMax ?? this.radiusMax,
       orientation: orientation ?? this.orientation,
       tilt: tilt ?? this.tilt,
-      synthesized: synthesized ?? this.synthesized,
-      transform: transform ?? this.transform,
-      original: original as PointerMoveEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
   }
 }
 
@@ -1786,7 +1780,7 @@
 ///
 ///  * [Listener.onPointerUp], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerUpEvent extends PointerEvent {
+class PointerUpEvent extends PointerEvent with _PointerEventDescription, _CopyPointerUpEvent {
   /// Creates a pointer up event.
   ///
   /// All of the arguments must be non-null.
@@ -1796,7 +1790,6 @@
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     int buttons = 0,
     bool obscured = false,
     // Allow pressure customization here because PointerUpEvent can contain
@@ -1813,8 +1806,6 @@
     double radiusMax = 0.0,
     double orientation = 0.0,
     double tilt = 0.0,
-    Matrix4? transform,
-    PointerUpEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
@@ -1822,7 +1813,6 @@
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          buttons: buttons,
          down: false,
          obscured: obscured,
@@ -1838,8 +1828,6 @@
          radiusMax: radiusMax,
          orientation: orientation,
          tilt: tilt,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -1848,88 +1836,22 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    return PointerUpEvent(
-      timeStamp: timeStamp,
-      pointer: pointer,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      buttons: buttons,
-      obscured: obscured,
-      pressure: pressure,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      transform: transform,
-      original: original as PointerUpEvent? ?? this,
-      embedderId: embedderId,
-    );
+    return _TransformedPointerUpEvent(original as PointerUpEvent? ?? this, transform);
   }
+}
+
+class _TransformedPointerUpEvent extends _TransformedPointerEvent with _CopyPointerUpEvent implements PointerUpEvent {
+  _TransformedPointerUpEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
 
   @override
-  PointerUpEvent copyWith({
-    Duration? timeStamp,
-    int? pointer,
-    PointerDeviceKind? kind,
-    int? device,
-    Offset? position,
-    Offset? localPosition,
-    Offset? delta,
-    Offset? localDelta,
-    int? buttons,
-    bool? obscured,
-    double? pressure,
-    double? pressureMin,
-    double? pressureMax,
-    double? distance,
-    double? distanceMax,
-    double? size,
-    double? radiusMajor,
-    double? radiusMinor,
-    double? radiusMin,
-    double? radiusMax,
-    double? orientation,
-    double? tilt,
-    bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
-    int? embedderId,
-  }) {
-    return PointerUpEvent(
-      timeStamp: timeStamp ?? this.timeStamp,
-      pointer: pointer ?? this.pointer,
-      kind: kind ?? this.kind,
-      device: device ?? this.device,
-      position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
-      buttons: buttons ?? this.buttons,
-      obscured: obscured ?? this.obscured,
-      pressure: pressure ?? this.pressure,
-      pressureMin: pressureMin ?? this.pressureMin,
-      pressureMax: pressureMax ?? this.pressureMax,
-      distance: distance ?? this.distance,
-      distanceMax: distanceMax ?? this.distanceMax,
-      size: size ?? this.size,
-      radiusMajor: radiusMajor ?? this.radiusMajor,
-      radiusMinor: radiusMinor ?? this.radiusMinor,
-      radiusMin: radiusMin ?? this.radiusMin,
-      radiusMax: radiusMax ?? this.radiusMax,
-      orientation: orientation ?? this.orientation,
-      tilt: tilt ?? this.tilt,
-      transform: transform ?? this.transform,
-      original: original as PointerUpEvent? ?? this,
-      embedderId: embedderId ?? this.embedderId,
-    );
-  }
+  final PointerUpEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerUpEvent transformed(Matrix4? transform) => original.transformed(transform);
 }
 
 /// An event that corresponds to a discrete pointer signal.
@@ -1951,9 +1873,6 @@
     PointerDeviceKind kind = PointerDeviceKind.mouse,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
-    Matrix4? transform,
-    PointerSignalEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
@@ -1961,72 +1880,13 @@
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 }
 
-/// The pointer issued a scroll event.
-///
-/// Scrolling the scroll wheel on a mouse is an example of an event that
-/// would create a [PointerScrollEvent].
-///
-/// See also:
-///
-///  * [Listener.onPointerSignal], which allows callers to be notified of these
-///    events in a widget tree.
-class PointerScrollEvent extends PointerSignalEvent {
-  /// Creates a pointer scroll event.
-  ///
-  /// All of the arguments must be non-null.
-  const PointerScrollEvent({
-    Duration timeStamp = Duration.zero,
-    PointerDeviceKind kind = PointerDeviceKind.mouse,
-    int device = 0,
-    Offset position = Offset.zero,
-    Offset? localPosition,
-    this.scrollDelta = Offset.zero,
-    Matrix4? transform,
-    PointerScrollEvent? original,
-    int embedderId = 0,
-  }) : assert(timeStamp != null),
-       assert(kind != null),
-       assert(device != null),
-       assert(position != null),
-       assert(scrollDelta != null),
-       super(
-         timeStamp: timeStamp,
-         kind: kind,
-         device: device,
-         position: position,
-         localPosition: localPosition,
-         transform: transform,
-         original: original,
-         embedderId: embedderId,
-       );
-
+mixin _CopyPointerScrollEvent on PointerEvent {
   /// The amount to scroll, in logical pixels.
-  final Offset scrollDelta;
-
-  @override
-  PointerScrollEvent transformed(Matrix4? transform) {
-    if (transform == null || transform == this.transform) {
-      return this;
-    }
-    return PointerScrollEvent(
-      timeStamp: timeStamp,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      scrollDelta: scrollDelta,
-      transform: transform,
-      original: original as PointerScrollEvent? ?? this,
-      embedderId: embedderId,
-    );
-  }
+  Offset get scrollDelta;
 
   @override
   PointerScrollEvent copyWith({
@@ -2035,9 +1895,7 @@
     PointerDeviceKind? kind,
     int? device,
     Offset? position,
-    Offset? localPosition,
     Offset? delta,
-    Offset? localDelta,
     int? buttons,
     bool? obscured,
     double? pressure,
@@ -2053,8 +1911,6 @@
     double? orientation,
     double? tilt,
     bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
     int? embedderId,
   }) {
     return PointerScrollEvent(
@@ -2062,12 +1918,54 @@
       kind: kind ?? this.kind,
       device: device ?? this.device,
       position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
       scrollDelta: scrollDelta,
-      transform: transform ?? this.transform,
-      original: original as PointerScrollEvent? ?? this,
       embedderId: embedderId ?? this.embedderId,
-    );
+    ).transformed(transform);
+  }
+}
+
+/// The pointer issued a scroll event.
+///
+/// Scrolling the scroll wheel on a mouse is an example of an event that
+/// would create a [PointerScrollEvent].
+///
+/// See also:
+///
+///  * [Listener.onPointerSignal], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerScrollEvent extends PointerSignalEvent with _PointerEventDescription, _CopyPointerScrollEvent {
+  /// Creates a pointer scroll event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerScrollEvent({
+    Duration timeStamp = Duration.zero,
+    PointerDeviceKind kind = PointerDeviceKind.mouse,
+    int device = 0,
+    Offset position = Offset.zero,
+    this.scrollDelta = Offset.zero,
+    int embedderId = 0,
+  }) : assert(timeStamp != null),
+       assert(kind != null),
+       assert(device != null),
+       assert(position != null),
+       assert(scrollDelta != null),
+       super(
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         embedderId: embedderId,
+       );
+
+  @override
+  final Offset scrollDelta;
+
+  @override
+  PointerScrollEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerScrollEvent(original as PointerScrollEvent? ?? this, transform);
   }
 
   @override
@@ -2077,13 +1975,86 @@
   }
 }
 
+class _TransformedPointerScrollEvent extends _TransformedPointerEvent with _CopyPointerScrollEvent implements PointerScrollEvent {
+  _TransformedPointerScrollEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerScrollEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  Offset get scrollDelta => original.scrollDelta;
+
+  @override
+  PointerScrollEvent transformed(Matrix4? transform) => original.transformed(transform);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('scrollDelta', scrollDelta));
+  }
+}
+
+mixin _CopyPointerCancelEvent on PointerEvent {
+  @override
+  PointerCancelEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerCancelEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
 /// The input from the pointer is no longer directed towards this receiver.
 ///
 /// See also:
 ///
 ///  * [Listener.onPointerCancel], which allows callers to be notified of these
 ///    events in a widget tree.
-class PointerCancelEvent extends PointerEvent {
+class PointerCancelEvent extends PointerEvent with _PointerEventDescription, _CopyPointerCancelEvent {
   /// Creates a pointer cancel event.
   ///
   /// All of the arguments must be non-null.
@@ -2093,7 +2064,6 @@
     PointerDeviceKind kind = PointerDeviceKind.touch,
     int device = 0,
     Offset position = Offset.zero,
-    Offset? localPosition,
     int buttons = 0,
     bool obscured = false,
     double pressureMin = 1.0,
@@ -2107,8 +2077,6 @@
     double radiusMax = 0.0,
     double orientation = 0.0,
     double tilt = 0.0,
-    Matrix4? transform,
-    PointerCancelEvent? original,
     int embedderId = 0,
   }) : super(
          timeStamp: timeStamp,
@@ -2116,7 +2084,6 @@
          kind: kind,
          device: device,
          position: position,
-         localPosition: localPosition,
          buttons: buttons,
          down: false,
          obscured: obscured,
@@ -2132,8 +2099,6 @@
          radiusMax: radiusMax,
          orientation: orientation,
          tilt: tilt,
-         transform: transform,
-         original: original,
          embedderId: embedderId,
        );
 
@@ -2142,85 +2107,7 @@
     if (transform == null || transform == this.transform) {
       return this;
     }
-    return PointerCancelEvent(
-      timeStamp: timeStamp,
-      pointer: pointer,
-      kind: kind,
-      device: device,
-      position: position,
-      localPosition: PointerEvent.transformPosition(transform, position),
-      buttons: buttons,
-      obscured: obscured,
-      pressureMin: pressureMin,
-      pressureMax: pressureMax,
-      distance: distance,
-      distanceMax: distanceMax,
-      size: size,
-      radiusMajor: radiusMajor,
-      radiusMinor: radiusMinor,
-      radiusMin: radiusMin,
-      radiusMax: radiusMax,
-      orientation: orientation,
-      tilt: tilt,
-      transform: transform,
-      original: original as PointerCancelEvent? ?? this,
-      embedderId: embedderId,
-    );
-  }
-
-  @override
-  PointerCancelEvent copyWith({
-    Duration? timeStamp,
-    int? pointer,
-    PointerDeviceKind? kind,
-    int? device,
-    Offset? position,
-    Offset? localPosition,
-    Offset? delta,
-    Offset? localDelta,
-    int? buttons,
-    bool? obscured,
-    double? pressure,
-    double? pressureMin,
-    double? pressureMax,
-    double? distance,
-    double? distanceMax,
-    double? size,
-    double? radiusMajor,
-    double? radiusMinor,
-    double? radiusMin,
-    double? radiusMax,
-    double? orientation,
-    double? tilt,
-    bool? synthesized,
-    Matrix4? transform,
-    PointerEvent? original,
-    int? embedderId,
-  }) {
-    return PointerCancelEvent(
-      timeStamp: timeStamp ?? this.timeStamp,
-      pointer: pointer ?? this.pointer,
-      kind: kind ?? this.kind,
-      device: device ?? this.device,
-      position: position ?? this.position,
-      localPosition: localPosition ?? this.localPosition,
-      buttons: buttons ?? this.buttons,
-      obscured: obscured ?? this.obscured,
-      pressureMin: pressureMin ?? this.pressureMin,
-      pressureMax: pressureMax ?? this.pressureMax,
-      distance: distance ?? this.distance,
-      distanceMax: distanceMax ?? this.distanceMax,
-      size: size ?? this.size,
-      radiusMajor: radiusMajor ?? this.radiusMajor,
-      radiusMinor: radiusMinor ?? this.radiusMinor,
-      radiusMin: radiusMin ?? this.radiusMin,
-      radiusMax: radiusMax ?? this.radiusMax,
-      orientation: orientation ?? this.orientation,
-      tilt: tilt ?? this.tilt,
-      transform: transform ?? this.transform,
-      original: original as PointerCancelEvent? ?? this,
-      embedderId: embedderId ?? this.embedderId,
-    );
+    return _TransformedPointerCancelEvent(original as PointerCancelEvent? ?? this, transform);
   }
 }
 
@@ -2262,3 +2149,17 @@
       return kScaleSlop;
   }
 }
+
+class _TransformedPointerCancelEvent extends _TransformedPointerEvent with _CopyPointerCancelEvent implements PointerCancelEvent {
+  _TransformedPointerCancelEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerCancelEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerCancelEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart
index b643d9b..9238ca3 100644
--- a/packages/flutter/lib/src/services/platform_views.dart
+++ b/packages/flutter/lib/src/services/platform_views.dart
@@ -568,27 +568,22 @@
     }
 
     int action;
-    switch (event.runtimeType) {
-      case PointerDownEvent:
-        action = numPointers == 1
-            ? AndroidViewController.kActionDown
-            : AndroidViewController.pointerAction(
-                pointerIdx, AndroidViewController.kActionPointerDown);
-        break;
-      case PointerUpEvent:
-        action = numPointers == 1
-            ? AndroidViewController.kActionUp
-            : AndroidViewController.pointerAction(
-                pointerIdx, AndroidViewController.kActionPointerUp);
-        break;
-      case PointerMoveEvent:
-        action = AndroidViewController.kActionMove;
-        break;
-      case PointerCancelEvent:
-        action = AndroidViewController.kActionCancel;
-        break;
-      default:
-        return null;
+    if (event is PointerDownEvent) {
+      action = numPointers == 1
+          ? AndroidViewController.kActionDown
+          : AndroidViewController.pointerAction(
+              pointerIdx, AndroidViewController.kActionPointerDown);
+    } else if (event is PointerUpEvent) {
+      action = numPointers == 1
+          ? AndroidViewController.kActionUp
+          : AndroidViewController.pointerAction(
+              pointerIdx, AndroidViewController.kActionPointerUp);
+    } else if (event is PointerMoveEvent) {
+      action = AndroidViewController.kActionMove;
+    } else if (event is PointerCancelEvent) {
+      action = AndroidViewController.kActionCancel;
+    } else {
+      return null;
     }
 
     return AndroidMotionEvent(
diff --git a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart
index df75989..1e46dbe 100644
--- a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart
+++ b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart
@@ -83,7 +83,7 @@
 
     await tester.pump(const Duration(milliseconds: 7));
     expect(events.length, 1);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
+    expect(events[0], isA<PointerDownEvent>());
     expect(events[0].timeStamp, currentTestFrameTime() + kSamplingOffset);
     expect(events[0].position, Offset(5.0 / ui.window.devicePixelRatio, 0.0));
 
@@ -91,7 +91,7 @@
     await tester.pump(const Duration(milliseconds: 2));
     expect(events.length, 2);
     expect(events[1].timeStamp, currentTestFrameTime() + kSamplingOffset);
-    expect(events[1].runtimeType, equals(PointerMoveEvent));
+    expect(events[1], isA<PointerMoveEvent>());
     expect(events[1].position, Offset(25.0 / ui.window.devicePixelRatio, 0.0));
     expect(events[1].delta, Offset(20.0 / ui.window.devicePixelRatio, 0.0));
 
@@ -99,11 +99,11 @@
     await tester.pump(const Duration(milliseconds: 2));
     expect(events.length, 4);
     expect(events[2].timeStamp, currentTestFrameTime() + kSamplingOffset);
-    expect(events[2].runtimeType, equals(PointerMoveEvent));
+    expect(events[2], isA<PointerMoveEvent>());
     expect(events[2].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
     expect(events[2].delta, Offset(15.0 / ui.window.devicePixelRatio, 0.0));
     expect(events[3].timeStamp, currentTestFrameTime() + kSamplingOffset);
-    expect(events[3].runtimeType, equals(PointerUpEvent));
+    expect(events[3], isA<PointerUpEvent>());
     expect(events[3].position, Offset(40.0 / ui.window.devicePixelRatio, 0.0));
   });
 }
diff --git a/packages/flutter/test/gestures/gesture_binding_test.dart b/packages/flutter/test/gestures/gesture_binding_test.dart
index cb38bb8..0faa04e 100644
--- a/packages/flutter/test/gestures/gesture_binding_test.dart
+++ b/packages/flutter/test/gestures/gesture_binding_test.dart
@@ -63,8 +63,8 @@
 
     ui.window.onPointerDataPacket(packet);
     expect(events.length, 2);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
-    expect(events[1].runtimeType, equals(PointerUpEvent));
+    expect(events[0], isA<PointerDownEvent>());
+    expect(events[1], isA<PointerUpEvent>());
   });
 
   test('Pointer move events', () {
@@ -81,9 +81,9 @@
 
     ui.window.onPointerDataPacket(packet);
     expect(events.length, 3);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
-    expect(events[1].runtimeType, equals(PointerMoveEvent));
-    expect(events[2].runtimeType, equals(PointerUpEvent));
+    expect(events[0], isA<PointerDownEvent>());
+    expect(events[1], isA<PointerMoveEvent>());
+    expect(events[2], isA<PointerUpEvent>());
   });
 
   test('Pointer hover events', () {
@@ -108,12 +108,12 @@
     expect(events.length, 0);
     expect(pointerRouterEvents.length, 6,
         reason: 'pointerRouterEvents contains: $pointerRouterEvents');
-    expect(pointerRouterEvents[0].runtimeType, equals(PointerAddedEvent));
-    expect(pointerRouterEvents[1].runtimeType, equals(PointerHoverEvent));
-    expect(pointerRouterEvents[2].runtimeType, equals(PointerHoverEvent));
-    expect(pointerRouterEvents[3].runtimeType, equals(PointerRemovedEvent));
-    expect(pointerRouterEvents[4].runtimeType, equals(PointerAddedEvent));
-    expect(pointerRouterEvents[5].runtimeType, equals(PointerHoverEvent));
+    expect(pointerRouterEvents[0], isA<PointerAddedEvent>());
+    expect(pointerRouterEvents[1], isA<PointerHoverEvent>());
+    expect(pointerRouterEvents[2], isA<PointerHoverEvent>());
+    expect(pointerRouterEvents[3], isA<PointerRemovedEvent>());
+    expect(pointerRouterEvents[4], isA<PointerAddedEvent>());
+    expect(pointerRouterEvents[5], isA<PointerHoverEvent>());
   });
 
   test('Pointer cancel events', () {
@@ -129,8 +129,8 @@
 
     ui.window.onPointerDataPacket(packet);
     expect(events.length, 2);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
-    expect(events[1].runtimeType, equals(PointerCancelEvent));
+    expect(events[0], isA<PointerDownEvent>());
+    expect(events[1], isA<PointerCancelEvent>());
   });
 
   test('Can cancel pointers', () {
@@ -150,8 +150,8 @@
 
     ui.window.onPointerDataPacket(packet);
     expect(events.length, 2);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
-    expect(events[1].runtimeType, equals(PointerCancelEvent));
+    expect(events[0], isA<PointerDownEvent>());
+    expect(events[1], isA<PointerCancelEvent>());
   });
 
   test('Can expand add and hover pointers', () {
@@ -169,11 +169,11 @@
       packet.data, ui.window.devicePixelRatio).toList();
 
     expect(events.length, 5);
-    expect(events[0].runtimeType, equals(PointerAddedEvent));
-    expect(events[1].runtimeType, equals(PointerHoverEvent));
-    expect(events[2].runtimeType, equals(PointerRemovedEvent));
-    expect(events[3].runtimeType, equals(PointerAddedEvent));
-    expect(events[4].runtimeType, equals(PointerHoverEvent));
+    expect(events[0], isA<PointerAddedEvent>());
+    expect(events[1], isA<PointerHoverEvent>());
+    expect(events[2], isA<PointerRemovedEvent>());
+    expect(events[3], isA<PointerAddedEvent>());
+    expect(events[4], isA<PointerHoverEvent>());
   });
 
   test('Can expand pointer scroll events', () {
@@ -188,8 +188,8 @@
       packet.data, ui.window.devicePixelRatio).toList();
 
     expect(events.length, 2);
-    expect(events[0].runtimeType, equals(PointerAddedEvent));
-    expect(events[1].runtimeType, equals(PointerScrollEvent));
+    expect(events[0], isA<PointerAddedEvent>());
+    expect(events[1], isA<PointerScrollEvent>());
   });
 
   test('Should synthesize kPrimaryButton for touch', () {
@@ -209,15 +209,15 @@
       packet.data, ui.window.devicePixelRatio).toList();
 
     expect(events.length, 5);
-    expect(events[0].runtimeType, equals(PointerAddedEvent));
+    expect(events[0], isA<PointerAddedEvent>());
     expect(events[0].buttons, equals(0));
-    expect(events[1].runtimeType, equals(PointerHoverEvent));
+    expect(events[1], isA<PointerHoverEvent>());
     expect(events[1].buttons, equals(0));
-    expect(events[2].runtimeType, equals(PointerDownEvent));
+    expect(events[2], isA<PointerDownEvent>());
     expect(events[2].buttons, equals(kPrimaryButton));
-    expect(events[3].runtimeType, equals(PointerMoveEvent));
+    expect(events[3], isA<PointerMoveEvent>());
     expect(events[3].buttons, equals(kPrimaryButton));
-    expect(events[4].runtimeType, equals(PointerUpEvent));
+    expect(events[4], isA<PointerUpEvent>());
     expect(events[4].buttons, equals(0));
   });
 
@@ -242,15 +242,15 @@
         packet.data, ui.window.devicePixelRatio).toList();
 
       expect(events.length, 5);
-      expect(events[0].runtimeType, equals(PointerAddedEvent));
+      expect(events[0], isA<PointerAddedEvent>());
       expect(events[0].buttons, equals(0));
-      expect(events[1].runtimeType, equals(PointerHoverEvent));
+      expect(events[1], isA<PointerHoverEvent>());
       expect(events[1].buttons, equals(0));
-      expect(events[2].runtimeType, equals(PointerDownEvent));
+      expect(events[2], isA<PointerDownEvent>());
       expect(events[2].buttons, equals(kPrimaryButton));
-      expect(events[3].runtimeType, equals(PointerMoveEvent));
+      expect(events[3], isA<PointerMoveEvent>());
       expect(events[3].buttons, equals(kPrimaryButton | kSecondaryStylusButton));
-      expect(events[4].runtimeType, equals(PointerUpEvent));
+      expect(events[4], isA<PointerUpEvent>());
       expect(events[4].buttons, equals(0));
     }
   });
@@ -272,15 +272,15 @@
       packet.data, ui.window.devicePixelRatio).toList();
 
     expect(events.length, 5);
-    expect(events[0].runtimeType, equals(PointerAddedEvent));
+    expect(events[0], isA<PointerAddedEvent>());
     expect(events[0].buttons, equals(0));
-    expect(events[1].runtimeType, equals(PointerHoverEvent));
+    expect(events[1], isA<PointerHoverEvent>());
     expect(events[1].buttons, equals(0));
-    expect(events[2].runtimeType, equals(PointerDownEvent));
+    expect(events[2], isA<PointerDownEvent>());
     expect(events[2].buttons, equals(kPrimaryButton));
-    expect(events[3].runtimeType, equals(PointerMoveEvent));
+    expect(events[3], isA<PointerMoveEvent>());
     expect(events[3].buttons, equals(kPrimaryButton));
-    expect(events[4].runtimeType, equals(PointerUpEvent));
+    expect(events[4], isA<PointerUpEvent>());
     expect(events[4].buttons, equals(0));
   });
 
@@ -303,15 +303,15 @@
         packet.data, ui.window.devicePixelRatio).toList();
 
       expect(events.length, 5);
-      expect(events[0].runtimeType, equals(PointerAddedEvent));
+      expect(events[0], isA<PointerAddedEvent>());
       expect(events[0].buttons, equals(0));
-      expect(events[1].runtimeType, equals(PointerHoverEvent));
+      expect(events[1], isA<PointerHoverEvent>());
       expect(events[1].buttons, equals(0));
-      expect(events[2].runtimeType, equals(PointerDownEvent));
+      expect(events[2], isA<PointerDownEvent>());
       expect(events[2].buttons, equals(kMiddleMouseButton));
-      expect(events[3].runtimeType, equals(PointerMoveEvent));
+      expect(events[3], isA<PointerMoveEvent>());
       expect(events[3].buttons, equals(kMiddleMouseButton | kSecondaryMouseButton));
-      expect(events[4].runtimeType, equals(PointerUpEvent));
+      expect(events[4], isA<PointerUpEvent>());
       expect(events[4].buttons, equals(0));
     }
   });
diff --git a/packages/flutter/test/gestures/locking_test.dart b/packages/flutter/test/gestures/locking_test.dart
index 4c8e0cd..0335d6f 100644
--- a/packages/flutter/test/gestures/locking_test.dart
+++ b/packages/flutter/test/gestures/locking_test.dart
@@ -59,7 +59,7 @@
     });
     expect(tested, isTrue);
     expect(events.length, 2);
-    expect(events[0].runtimeType, equals(PointerDownEvent));
-    expect(events[1].runtimeType, equals(PointerUpEvent));
+    expect(events[0], isA<PointerDownEvent>());
+    expect(events[1], isA<PointerUpEvent>());
   });
 }
diff --git a/packages/flutter/test/rendering/mouse_tracking_test.dart b/packages/flutter/test/rendering/mouse_tracking_test.dart
index 956f89b..e80b152 100644
--- a/packages/flutter/test/rendering/mouse_tracking_test.dart
+++ b/packages/flutter/test/rendering/mouse_tracking_test.dart
@@ -82,6 +82,8 @@
     _binding.postFrameCallbacks.clear();
   });
 
+  final Matrix4 translate10by20 = Matrix4.translationValues(10, 20, 0);
+
   test('MouseTrackerAnnotation has correct toString', () {
     final MouseTrackerAnnotation annotation1 = MouseTrackerAnnotation(
       onEnter: (_) {},
@@ -125,8 +127,8 @@
     ]));
     addTearDown(() => dispatchRemoveDevice());
 
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 0.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 0.0))),
     ]));
     expect(listenerLogs, <bool>[true]);
     events.clear();
@@ -136,8 +138,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.hover, const Offset(1.0, 101.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerHoverEvent(position: Offset(1.0, 101.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(1.0, 101.0))),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     expect(listenerLogs, isEmpty);
@@ -147,8 +149,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.remove, const Offset(1.0, 101.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(1.0, 101.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(1.0, 101.0))),
     ]));
     expect(listenerLogs, <bool>[false]);
     events.clear();
@@ -158,8 +160,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.add, const Offset(0.0, 301.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 301.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 301.0))),
     ]));
     expect(listenerLogs, <bool>[true]);
     events.clear();
@@ -177,9 +179,9 @@
       _pointerData(PointerChange.add, const Offset(0.0, 0.0)),
       _pointerData(PointerChange.hover, const Offset(0.0, 1.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 0.0)),
-      const PointerHoverEvent(position: Offset(0.0, 1.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 0.0))),
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(0.0, 1.0))),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -189,9 +191,9 @@
       _pointerData(PointerChange.add, const Offset(0.0, 401.0), device: 1),
       _pointerData(PointerChange.hover, const Offset(1.0, 401.0), device: 1),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 401.0), device: 1),
-      const PointerHoverEvent(position: Offset(1.0, 401.0), device: 1),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 401.0), device: 1)),
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(1.0, 401.0), device: 1)),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -200,8 +202,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.hover, const Offset(0.0, 101.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerHoverEvent(position: Offset(0.0, 101.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(0.0, 101.0))),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -210,8 +212,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.hover, const Offset(1.0, 501.0), device: 1),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerHoverEvent(position: Offset(1.0, 501.0), device: 1),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(1.0, 501.0), device: 1)),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -220,8 +222,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.remove, const Offset(0.0, 101.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(0.0, 101.0)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(0.0, 101.0))),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -230,8 +232,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.hover, const Offset(1.0, 601.0), device: 1),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerHoverEvent(position: Offset(1.0, 601.0), device: 1),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(1.0, 601.0), device: 1)),
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -240,8 +242,8 @@
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.remove, const Offset(1.0, 601.0), device: 1),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(1.0, 601.0), device: 1),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(1.0, 601.0), device: 1)),
     ]));
     expect(_mouseTracker.mouseIsConnected, isFalse);
     events.clear();
@@ -256,24 +258,24 @@
       _pointerData(PointerChange.down, const Offset(0.0, 101.0)),
     ]));
     addTearDown(() => dispatchRemoveDevice());
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
       // This Enter event is triggered by the [PointerAddedEvent] The
       // [PointerDownEvent] is ignored by [MouseTracker].
-      const PointerEnterEvent(position: Offset(0.0, 101.0)),
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 101.0))),
     ]));
     events.clear();
 
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.move, const Offset(0.0, 201.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
     ]));
     events.clear();
 
     ui.window.onPointerDataPacket(ui.PointerDataPacket(data: <ui.PointerData>[
       _pointerData(PointerChange.up, const Offset(0.0, 301.0)),
     ]));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
     ]));
     events.clear();
   });
@@ -299,7 +301,7 @@
       _pointerData(PointerChange.add, const Offset(0.0, 100.0)),
     ]));
     addTearDown(() => dispatchRemoveDevice());
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
     ]));
     expect(_mouseTracker.mouseIsConnected, isTrue);
     events.clear();
@@ -310,8 +312,8 @@
     expect(_binding.postFrameCallbacks, hasLength(1));
 
     _binding.flushPostFrameCallbacks(Duration.zero);
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0, 100), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0, 100)).transformed(translate10by20)),
     ]));
     events.clear();
 
@@ -321,8 +323,8 @@
     expect(_binding.postFrameCallbacks, hasLength(1));
 
     _binding.flushPostFrameCallbacks(Duration.zero);
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
     expect(_binding.postFrameCallbacks, hasLength(0));
   });
@@ -357,8 +359,8 @@
     expect(_binding.postFrameCallbacks, hasLength(1));
 
     _binding.flushPostFrameCallbacks(Duration.zero);
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
     events.clear();
 
@@ -371,8 +373,8 @@
     expect(_binding.postFrameCallbacks, hasLength(1));
 
     _binding.flushPostFrameCallbacks(Duration.zero);
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
     expect(_binding.postFrameCallbacks, hasLength(0));
   });
@@ -400,8 +402,8 @@
     ]));
 
     expect(_binding.postFrameCallbacks, hasLength(0));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
     events.clear();
 
@@ -410,8 +412,8 @@
       _pointerData(PointerChange.remove, const Offset(0.0, 100.0)),
     ]));
     expect(_binding.postFrameCallbacks, hasLength(0));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
   });
 
@@ -444,9 +446,9 @@
       _pointerData(PointerChange.hover, const Offset(0.0, 100.0)),
     ]));
     expect(_binding.postFrameCallbacks, hasLength(0));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerEnterEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
-      const PointerHoverEvent(position: Offset(0.0, 100.0), localPosition: Offset(10, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerEnterEvent>(const PointerEnterEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
+      EventMatcher<PointerHoverEvent>(const PointerHoverEvent(position: Offset(0.0, 100.0)).transformed(translate10by20)),
     ]));
     events.clear();
 
@@ -456,8 +458,8 @@
       _pointerData(PointerChange.hover, const Offset(200.0, 100.0)),
     ]));
     expect(_binding.postFrameCallbacks, hasLength(0));
-    expect(events, _equalToEventsOnCriticalFields(<PointerEvent>[
-      const PointerExitEvent(position: Offset(200.0, 100.0), localPosition: Offset(210, 120)),
+    expect(events, _equalToEventsOnCriticalFields(<BaseEventMatcher>[
+      EventMatcher<PointerExitEvent>(const PointerExitEvent(position: Offset(200.0, 100.0)).transformed(translate10by20)),
     ]));
   });
 
@@ -629,11 +631,11 @@
   );
 }
 
-class _EventCriticalFieldsMatcher extends Matcher {
-  _EventCriticalFieldsMatcher(this._expected)
-    : assert(_expected != null);
+class BaseEventMatcher extends Matcher {
+  BaseEventMatcher(this.expected)
+    : assert(expected != null);
 
-  final PointerEvent _expected;
+  final PointerEvent expected;
 
   bool _matchesField(Map<dynamic, dynamic> matchState, String field,
       dynamic actual, dynamic expected) {
@@ -650,16 +652,12 @@
 
   @override
   bool matches(dynamic untypedItem, Map<dynamic, dynamic> matchState) {
-    if (untypedItem.runtimeType != _expected.runtimeType) {
-      return false;
-    }
-
     final PointerEvent actual = untypedItem as PointerEvent;
     if (!(
       _matchesField(matchState, 'kind', actual.kind, PointerDeviceKind.mouse) &&
-      _matchesField(matchState, 'position', actual.position, _expected.position) &&
-      _matchesField(matchState, 'device', actual.device, _expected.device) &&
-      _matchesField(matchState, 'localPosition', actual.localPosition, _expected.localPosition)
+      _matchesField(matchState, 'position', actual.position, expected.position) &&
+      _matchesField(matchState, 'device', actual.device, expected.device) &&
+      _matchesField(matchState, 'localPosition', actual.localPosition, expected.localPosition)
     )) {
       return false;
     }
@@ -670,7 +668,7 @@
   Description describe(Description description) {
     return description
       .add('event (critical fields only) ')
-      .addDescriptionOf(_expected);
+      .addDescriptionOf(expected);
   }
 
   @override
@@ -680,13 +678,6 @@
     Map<dynamic, dynamic> matchState,
     bool verbose,
   ) {
-    if (item.runtimeType != _expected.runtimeType) {
-      return mismatchDescription
-        .add('is ')
-        .addDescriptionOf(item.runtimeType)
-        .add(" and doesn't match ")
-        .addDescriptionOf(_expected.runtimeType);
-    }
     return mismatchDescription
       .add('has ')
       .addDescriptionOf(matchState['actual'])
@@ -695,10 +686,40 @@
   }
 }
 
+class EventMatcher<T extends PointerEvent> extends BaseEventMatcher {
+  EventMatcher(T expected) : super(expected);
+
+  @override
+  bool matches(dynamic untypedItem, Map<dynamic, dynamic> matchState) {
+    if (untypedItem is! T) {
+      return false;
+    }
+
+    return super.matches(untypedItem, matchState);
+  }
+
+  @override
+  Description describeMismatch(
+    dynamic item,
+    Description mismatchDescription,
+    Map<dynamic, dynamic> matchState,
+    bool verbose,
+  ) {
+    if (item is! T) {
+      return mismatchDescription
+        .add('is ')
+        .addDescriptionOf(item.runtimeType)
+        .add(' and is not a subtype of ')
+        .addDescriptionOf(T);
+    }
+    return super.describeMismatch(item, mismatchDescription, matchState, verbose);
+  }
+}
+
 class _EventListCriticalFieldsMatcher extends Matcher {
   _EventListCriticalFieldsMatcher(this._expected);
 
-  final Iterable<PointerEvent> _expected;
+  final Iterable<BaseEventMatcher> _expected;
 
   @override
   bool matches(dynamic untypedItem, Map<dynamic, dynamic> matchState) {
@@ -709,15 +730,14 @@
     if (item.length != _expected.length)
       return false;
     int i = 0;
-    for (final PointerEvent e in _expected) {
+    for (final BaseEventMatcher matcher in _expected) {
       iterator.moveNext();
-      final Matcher matcher = _EventCriticalFieldsMatcher(e);
       final Map<dynamic, dynamic> subState = <dynamic, dynamic>{};
       final PointerEvent actual = iterator.current;
       if (!matcher.matches(actual, subState)) {
         addStateInfo(matchState, <dynamic, dynamic>{
           'index': i,
-          'expected': e,
+          'expected': matcher.expected,
           'actual': actual,
           'matcher': matcher,
           'state': subState,
@@ -769,6 +789,6 @@
   }
 }
 
-Matcher _equalToEventsOnCriticalFields(List<PointerEvent> source) {
+Matcher _equalToEventsOnCriticalFields(List<BaseEventMatcher> source) {
   return _EventListCriticalFieldsMatcher(source);
 }
diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart
index 4850d7e..431d31e 100644
--- a/packages/flutter/test/widgets/platform_view_test.dart
+++ b/packages/flutter/test/widgets/platform_view_test.dart
@@ -2552,7 +2552,7 @@
     await gesture.moveTo(const Offset(400, 300));
     expect(logs, <String>['enter1']);
     expect(controller.dispatchedPointerEvents, hasLength(1));
-    expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
+    expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>());
     logs.clear();
     controller.dispatchedPointerEvents.clear();
 
@@ -2582,7 +2582,7 @@
     await gesture.moveBy(const Offset(1, 1));
     expect(logs, isEmpty);
     expect(controller.dispatchedPointerEvents, hasLength(1));
-    expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
+    expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>());
     expect(controller.dispatchedPointerEvents[0].position, const Offset(401, 301));
     expect(controller.dispatchedPointerEvents[0].localPosition, const Offset(101, 101));
     controller.dispatchedPointerEvents.clear();
@@ -2617,6 +2617,6 @@
     await gesture.moveBy(const Offset(1, 1));
     expect(logs, isEmpty);
     expect(controller.dispatchedPointerEvents, hasLength(1));
-    expect(controller.dispatchedPointerEvents[0].runtimeType, PointerHoverEvent);
+    expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>());
   });
 }