Skip to content

Commit c1f6ceb

Browse files
authored
Add a DrivenScrollActivity.simulation constructor (flutter#166730)
I'd like to have a scroll activity that drives the scroll view through a particular animation -- just like a DrivenScrollActivity does -- but where the animation doesn't have an end point or duration that's known when the animation begins. (Concretely, this is to implement a "scroll to the end of history" button in the Zulip message list. The end point is maxScrollExtent... except that maxScrollExtent while in the middle of history is only an estimate, and will change as the list view scrolls through shorter and longer messages.) That means the animation is naturally described by a Simulation, but not by the parameters accepted by the current DrivenScrollActivity constructor, or by the animateTo method which wraps it. I think this can be handled quite cleanly with an alternate constructor on DrivenScrollActivity, one that accepts a Simulation instead of the from/to/duration/curve parameters accepted by the default constructor. The new constructor is very similar to the constructor of BallisticScrollActivity. Most of the changes here are in revising the docs of both DrivenScrollActivity and BallisticScrollActivity. The docs had characterized the difference between the two as about using a Simulation vs. the from/to/duration/curve animation parameters, but I think that's never been the most important difference between them: the key difference is really the `goBallistic` calls in BallisticScrollActivity, particularly in `applyNewDimensions`, and the implications those have for how the simulation needs to relate to the scroll physics. So this rewrites the docs to describe that.
1 parent d9d9df4 commit c1f6ceb

File tree

2 files changed

+80
-11
lines changed

2 files changed

+80
-11
lines changed

packages/flutter/lib/src/widgets/scroll_activity.dart

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -557,21 +557,36 @@ class DragScrollActivity extends ScrollActivity {
557557
}
558558
}
559559

560-
/// An activity that animates a scroll view based on a physics [Simulation].
560+
/// The activity a scroll view performs after being set into motion.
561561
///
562-
/// A [BallisticScrollActivity] is typically used when the user lifts their
563-
/// finger off the screen to continue the scrolling gesture with the current velocity.
562+
/// For example, a [BallisticScrollActivity] is used when the user
563+
/// lifts their finger off the screen after a [DragScrollActivity],
564+
/// to continue the scrolling motion starting from the current velocity.
564565
///
565566
/// [BallisticScrollActivity] is also used to restore a scroll view to a valid
566567
/// scroll offset when the geometry of the scroll view changes. In these
567568
/// situations, the [Simulation] typically starts with a zero velocity.
568569
///
570+
/// The scrolling will be driven by the given [Simulation]. If a
571+
/// [BallisticScrollActivity] is in progress when the scroll metrics change,
572+
/// then the activity will be replaced with a new ballistic activity starting
573+
/// from the current velocity (see [ScrollPhysics.createBallisticSimulation]).
574+
/// To ensure the user perceives smooth motion across such a change,
575+
/// the simulation should typically be the result
576+
/// of [ScrollPhysics.createBallisticSimulation]
577+
/// for the scroll physics of the scroll view.
578+
///
569579
/// See also:
570580
///
571-
/// * [DrivenScrollActivity], which animates a scroll view based on a set of
572-
/// animation parameters.
581+
/// * [DrivenScrollActivity], which drives a scroll view through
582+
/// a given animation, without resetting to a ballistic simulation
583+
/// when scroll metrics change.
573584
class BallisticScrollActivity extends ScrollActivity {
574-
/// Creates an activity that animates a scroll view based on a [simulation].
585+
/// Creates an activity that sets into motion a scroll view.
586+
///
587+
/// The simulation should typically be the result
588+
/// of [ScrollPhysics.createBallisticSimulation]
589+
/// for the scroll physics of the scroll view.
575590
BallisticScrollActivity(
576591
super.delegate,
577592
Simulation simulation,
@@ -662,18 +677,24 @@ class BallisticScrollActivity extends ScrollActivity {
662677
}
663678
}
664679

665-
/// An activity that animates a scroll view based on animation parameters.
680+
/// An activity that drives a scroll view through a given animation.
666681
///
667682
/// For example, a [DrivenScrollActivity] is used to implement
668683
/// [ScrollController.animateTo].
669684
///
685+
/// The scrolling will be driven by the given animation parameters
686+
/// or the given [Simulation].
687+
///
688+
/// Unlike a [BallisticScrollActivity], if a [DrivenScrollActivity] is
689+
/// in progress when the scroll metrics change, the activity will continue
690+
/// with its original animation.
691+
///
670692
/// See also:
671693
///
672-
/// * [BallisticScrollActivity], which animates a scroll view based on a
673-
/// physics [Simulation].
694+
/// * [BallisticScrollActivity], which sets into motion a scroll view.
674695
class DrivenScrollActivity extends ScrollActivity {
675-
/// Creates an activity that animates a scroll view based on animation
676-
/// parameters.
696+
/// Creates an activity that drives a scroll view through an animation
697+
/// given by animation parameters.
677698
DrivenScrollActivity(
678699
super.delegate, {
679700
required double from,
@@ -697,6 +718,25 @@ class DrivenScrollActivity extends ScrollActivity {
697718
).whenComplete(_end); // won't trigger if we dispose _controller before it completes.
698719
}
699720

721+
/// Creates an activity that drives a scroll view through an animation
722+
/// given by a [Simulation].
723+
DrivenScrollActivity.simulation(
724+
super.delegate,
725+
Simulation simulation, {
726+
required TickerProvider vsync,
727+
}) {
728+
_completer = Completer<void>();
729+
_controller =
730+
AnimationController.unbounded(
731+
debugLabel: objectRuntimeType(this, 'DrivenScrollActivity'),
732+
vsync: vsync,
733+
)
734+
..addListener(_tick)
735+
..animateWith(
736+
simulation,
737+
).whenComplete(_end); // won't trigger if we dispose _controller before it completes.
738+
}
739+
700740
late final Completer<void> _completer;
701741
late final AnimationController _controller;
702742

packages/flutter/test/widgets/scroll_activity_test.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:flutter/gestures.dart';
66
import 'package:flutter/material.dart';
7+
import 'package:flutter/physics.dart';
78
import 'package:flutter/scheduler.dart';
89
import 'package:flutter_test/flutter_test.dart';
910
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
@@ -246,6 +247,34 @@ void main() {
246247
await tester.pumpAndSettle();
247248
});
248249

250+
testWidgets('DrivenScrollActivity.simulation constructor', (WidgetTester tester) async {
251+
final ScrollController controller = ScrollController();
252+
addTearDown(controller.dispose);
253+
await tester.pumpWidget(
254+
Directionality(
255+
textDirection: TextDirection.ltr,
256+
child: ListView(controller: controller, children: children(10)),
257+
),
258+
);
259+
final ScrollPositionWithSingleContext position =
260+
controller.position as ScrollPositionWithSingleContext;
261+
262+
const double g = 9.8;
263+
position.beginActivity(
264+
DrivenScrollActivity.simulation(
265+
position,
266+
vsync: position.context.vsync,
267+
GravitySimulation(g, 0, 1000, 0),
268+
),
269+
);
270+
await tester.pump();
271+
expect(position.pixels, 0.0);
272+
await tester.pump(const Duration(seconds: 1));
273+
expect(position.pixels, (1 / 2) * g);
274+
await tester.pump(const Duration(seconds: 1));
275+
expect(position.pixels, 2 * g);
276+
});
277+
249278
test('$ScrollActivity dispatches memory events', () async {
250279
await expectLater(
251280
await memoryEvents(

0 commit comments

Comments
 (0)