Fix refresh control in the gallery demo, update comments (#30129)
- Fixed the bug where CupertinoRefreshControl doesn't work in the gallery demo on Android.
- Updated documentation on CupertinoRefreshControl
- Added comments to the gallery demo
- Added concrete examples to ScrollPhysics
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 a3a7385..6fa26d0 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 @@
: CupertinoColors.darkBackgroundGray,
),
child: CustomScrollView(
+ // If left unspecified, the [CustomScrollView] appends an
+ // [AlwaysScrollableScrollPhysics]. Behind the scene, the ScrollableState
+ // will attach that [AlwaysScrollableScrollPhysics] to the output of
+ // [ScrollConfiguration.of] which will be a [ClampingScrollPhysics]
+ // on Android.
+ // To demonstrate the iOS behavior in this demo and to ensure that the list
+ // always scrolls, we specifically use a [BouncingScrollPhysics] combined
+ // with a [AlwaysScrollableScrollPhysics]
+ physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: const Text('Refresh'),
diff --git a/examples/flutter_gallery/test/smoke_test.dart b/examples/flutter_gallery/test/smoke_test.dart
index 284ee0f..9a15506 100644
--- a/examples/flutter_gallery/test/smoke_test.dart
+++ b/examples/flutter_gallery/test/smoke_test.dart
@@ -82,6 +82,9 @@
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 @@
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 30c712a..f54b613 100644
--- a/packages/flutter/lib/src/cupertino/refresh.dart
+++ b/packages/flutter/lib/src/cupertino/refresh.dart
@@ -248,8 +248,17 @@
/// 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 089b407..3540ec7 100644
--- a/packages/flutter/lib/src/widgets/scroll_physics.dart
+++ b/packages/flutter/lib/src/widgets/scroll_physics.dart
@@ -15,6 +15,14 @@
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 @@
/// 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 @@
/// 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 @@
/// 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]