diff --git a/examples/flutter_gallery/lib/demo/cupertino/cupertino_refresh_demo.dart b/examples/flutter_gallery/lib/demo/cupertino/cupertino_refresh_demo.dart index a3a73858746..6fa26d03d16 100644 --- a/examples/flutter_gallery/lib/demo/cupertino/cupertino_refresh_demo.dart +++ b/examples/flutter_gallery/lib/demo/cupertino/cupertino_refresh_demo.dart @@ -48,6 +48,15 @@ class _CupertinoRefreshControlDemoState extends State[ CupertinoSliverNavigationBar( largeTitle: const Text('Refresh'), diff --git a/examples/flutter_gallery/test/smoke_test.dart b/examples/flutter_gallery/test/smoke_test.dart index 284ee0f2da3..9a15506683e 100644 --- a/examples/flutter_gallery/test/smoke_test.dart +++ b/examples/flutter_gallery/test/smoke_test.dart @@ -82,6 +82,9 @@ Future smokeDemo(WidgetTester tester, GalleryDemo demo) async { verifyToStringOutput('debugDumpLayerTree', routeName, RendererBinding.instance?.renderView?.debugLayer?.toStringDeep()); // Scroll the demo around a bit more. + await tester.flingFrom(const Offset(400.0, 300.0), const Offset(0.0, 400.0), 1000.0); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); await tester.flingFrom(const Offset(400.0, 300.0), const Offset(-200.0, 0.0), 500.0); await tester.pump(); await tester.pump(const Duration(milliseconds: 50)); @@ -90,9 +93,6 @@ Future smokeDemo(WidgetTester tester, GalleryDemo demo) async { await tester.flingFrom(const Offset(400.0, 300.0), const Offset(100.0, 0.0), 500.0); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); - await tester.flingFrom(const Offset(400.0, 300.0), const Offset(0.0, 400.0), 1000.0); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 400)); // Go back await tester.pageBack(); diff --git a/packages/flutter/lib/src/cupertino/refresh.dart b/packages/flutter/lib/src/cupertino/refresh.dart index 30c712aa4fe..f54b6133c4e 100644 --- a/packages/flutter/lib/src/cupertino/refresh.dart +++ b/packages/flutter/lib/src/cupertino/refresh.dart @@ -248,8 +248,17 @@ typedef RefreshCallback = Future Function(); /// and the indicator sliver has retracted at least 90% of the way back. /// /// Can only be used in downward-scrolling vertical lists that overscrolls. In -/// other words, refreshes can't be triggered with lists using -/// [ClampingScrollPhysics]. +/// other words, refreshes can't be triggered with [Scrollable]s using +/// [ClampingScrollPhysics] which is the default on Android. To allow overscroll +/// on Android, use an overscrolling physics such as [BouncingScrollPhysics]. +/// This can be done via: +/// +/// * Providing a [BouncingScrollPhysics] (possibly in combination with a +/// [AlwaysScrollableScrollPhysics]) while constructing the scrollable. +/// * By inserting a [ScrollConfiguration] with [BouncingScrollPhysics] above +/// the scrollable. +/// * By using [CupertinoApp], which always uses a [ScrollConfiguration] +/// with [BouncingScrollPhysics] regardless of platform. /// /// In a typical application, this sliver should be inserted between the app bar /// sliver such as [CupertinoSliverNavigationBar] and your main scrollable diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 089b4079adb..3540ec7bd91 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -15,6 +15,14 @@ import 'scroll_simulation.dart'; export 'package:flutter/physics.dart' show Simulation, ScrollSpringSimulation, Tolerance; +// Examples can assume: +// class FooScrollPhysics extends ScrollPhysics { +// const FooScrollPhysics({ ScrollPhysics parent }): super(parent: parent); +// } +// class BarScrollPhysics extends ScrollPhysics { +// const BarScrollPhysics({ ScrollPhysics parent }): super(parent: parent); +// } + /// Determines the physics of a [Scrollable] widget. /// /// For example, determines how the [Scrollable] will behave when the user @@ -24,6 +32,9 @@ export 'package:flutter/physics.dart' show Simulation, ScrollSpringSimulation, T /// velocity are used as the initial conditions for the particle in the /// simulation. The movement of the particle in the simulation is then used to /// determine the scroll position for the widget. +/// +/// Instead of creating your own subclasses, [parent] can be used to combine +/// [ScrollPhysics] objects of different types to get the desired scroll physics. @immutable class ScrollPhysics { /// Creates an object with the default scroll physics. @@ -34,7 +45,16 @@ class ScrollPhysics { /// If a subclass of [ScrollPhysics] does not override a method, that subclass /// will inherit an implementation from this base class that defers to /// [parent]. This mechanism lets you assemble novel combinations of - /// [ScrollPhysics] subclasses at runtime. + /// [ScrollPhysics] subclasses at runtime. For example: + /// + /// ```dart + /// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()) + /// + /// ``` + /// will result in a [ScrollPhysics] that has the combined behavior + /// of [BouncingScrollPhysics] and [AlwaysScrollableScrollPhysics]: + /// behaviors that are not specified in [BouncingScrollPhysics] + /// (e.g. [shouldAcceptUserOffset]) will defer to [AlwaysScrollableScrollPhysics]. final ScrollPhysics parent; /// If [parent] is null then return ancestor, otherwise recursively build a @@ -60,6 +80,18 @@ class ScrollPhysics { /// The returned object will combine some of the behaviors from this /// [ScrollPhysics] instance and some of the behaviors from [ancestor]. /// + /// {@tool sample} + /// + /// In the following example, the [applyTo] method is used to combine the + /// scroll physics of two [ScrollPhysics] objects, the resulting [ScrollPhysics] + /// `x` has the same behavior as `y`: + /// + /// ```dart + /// final FooScrollPhysics x = FooScrollPhysics().applyTo(BarScrollPhysics()); + /// const FooScrollPhysics y = FooScrollPhysics(parent: BarScrollPhysics()); + /// ``` + /// {@end-tool} + /// /// See also: /// /// * [buildParent], a utility method that's often used to define [applyTo]