Cancel DraggableScrollableSheet ballistic animation if a new activity begins (#86614)

diff --git a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
index eaaeca8..07a201c 100644
--- a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
+++ b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
@@ -10,6 +10,7 @@
 import 'inherited_notifier.dart';
 import 'layout_builder.dart';
 import 'notification_listener.dart';
+import 'scroll_activity.dart';
 import 'scroll_context.dart';
 import 'scroll_controller.dart';
 import 'scroll_notification.dart';
@@ -431,10 +432,18 @@
         );
 
   VoidCallback? _dragCancelCallback;
+  VoidCallback? _ballisticCancelCallback;
   final _DraggableSheetExtent extent;
   bool get listShouldScroll => pixels > 0.0;
 
   @override
+  void beginActivity(ScrollActivity? newActivity) {
+    // Cancel the running ballistic simulation, if there is one.
+    _ballisticCancelCallback?.call();
+    super.beginActivity(newActivity);
+  }
+
+  @override
   bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
     // We need to provide some extra extent if we haven't yet reached the max or
     // min extents. Otherwise, a list with fewer children than the extent of
@@ -481,6 +490,9 @@
       debugLabel: objectRuntimeType(this, '_DraggableScrollableSheetPosition'),
       vsync: context.vsync,
     );
+    // Stop the ballistic animation if a new activity starts.
+    // See: [beginActivity].
+    _ballisticCancelCallback = ballisticController.stop;
     double lastDelta = 0;
     void _tick() {
       final double delta = ballisticController.value - lastDelta;
@@ -501,7 +513,10 @@
     ballisticController
       ..addListener(_tick)
       ..animateWith(simulation).whenCompleteOrCancel(
-        ballisticController.dispose,
+        () {
+          _ballisticCancelCallback = null;
+          ballisticController.dispose();
+        },
       );
   }
 
diff --git a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
index e9e6adb..db71867 100644
--- a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
+++ b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
@@ -264,6 +264,40 @@
         expect(find.text('Item 70'), findsNothing);
       }, variant: TargetPlatformVariant.all());
 
+      testWidgets('Ballistic animation on fling can be interrupted', (WidgetTester tester) async {
+        int taps = 0;
+        await tester.pumpWidget(_boilerplate(() => taps++));
+
+        expect(find.text('TapHere'), findsOneWidget);
+        await tester.tap(find.text('TapHere'));
+        expect(taps, 1);
+        expect(find.text('Item 1'), findsOneWidget);
+        expect(find.text('Item 31'), findsNothing);
+        expect(find.text('Item 70'), findsNothing);
+
+        await tester.fling(find.text('Item 1'), const Offset(0, -200), 2000);
+        // Don't pump and settle because we want to interrupt the ballistic scrolling animation.
+        expect(find.text('TapHere'), findsOneWidget);
+        await tester.tap(find.text('TapHere'), warnIfMissed: false);
+        expect(taps, 2);
+        expect(find.text('Item 1'), findsOneWidget);
+        expect(find.text('Item 31'), findsOneWidget);
+        expect(find.text('Item 70'), findsNothing);
+
+        // Use `dragFrom` here because calling `drag` on a list item without
+        // first calling `pumpAndSettle` fails with a hit test error.
+        await tester.dragFrom(const Offset(0, 200), const Offset(0, 200));
+        await tester.pumpAndSettle();
+
+        // Verify that the ballistic animation has canceled and the sheet has
+        // returned to it's original position.
+        await tester.tap(find.text('TapHere'));
+        expect(taps, 3);
+        expect(find.text('Item 1'), findsOneWidget);
+        expect(find.text('Item 31'), findsNothing);
+        expect(find.text('Item 70'), findsNothing);
+      }, variant: TargetPlatformVariant.all());
+
       debugDefaultTargetPlatformOverride = null;
     });