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]