Commit 89856c0e authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Adjust the defaults behaviour of scroll views. (#9679)

* Adjust the defaults behaviour of scroll views.

Now, primary scroll views scroll by default. Others only scroll if necessary.

* apply suggested changes
parent f739e9e0
...@@ -42,6 +42,7 @@ class _ListDemoState extends State<ListDemo> { ...@@ -42,6 +42,7 @@ class _ListDemoState extends State<ListDemo> {
), ),
child: new ListView( child: new ListView(
shrinkWrap: true, shrinkWrap: true,
primary: false,
children: <Widget>[ children: <Widget>[
new ListTile( new ListTile(
dense: true, dense: true,
......
...@@ -298,6 +298,6 @@ class GalleryDrawer extends StatelessWidget { ...@@ -298,6 +298,6 @@ class GalleryDrawer extends StatelessWidget {
)); ));
} }
return new Drawer(child: new ListView(children: allDrawerItems)); return new Drawer(child: new ListView(primary: false, children: allDrawerItems));
} }
} }
...@@ -215,7 +215,7 @@ class _PagePosition extends ScrollPositionWithSingleContext { ...@@ -215,7 +215,7 @@ class _PagePosition extends ScrollPositionWithSingleContext {
/// These physics cause the page view to snap to page boundaries. /// These physics cause the page view to snap to page boundaries.
class PageScrollPhysics extends ScrollPhysics { class PageScrollPhysics extends ScrollPhysics {
/// Creates physics for a [PageView]. /// Creates physics for a [PageView].
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent); const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent); PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent);
......
...@@ -88,11 +88,6 @@ abstract class ScrollMetrics { ...@@ -88,11 +88,6 @@ abstract class ScrollMetrics {
/// of the viewport in the scrollable. This is the content below the content /// of the viewport in the scrollable. This is the content below the content
/// described by [extentInside]. /// described by [extentInside].
double get extentAfter => math.max(maxScrollExtent - pixels, 0.0); double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);
@override
String toString() {
return '$runtimeType(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)}})';
}
} }
/// An immutable snapshot of values associated with a [Scrollable] viewport. /// An immutable snapshot of values associated with a [Scrollable] viewport.
...@@ -131,4 +126,9 @@ class FixedScrollMetrics extends ScrollMetrics { ...@@ -131,4 +126,9 @@ class FixedScrollMetrics extends ScrollMetrics {
@override @override
final AxisDirection axisDirection; final AxisDirection axisDirection;
@override
String toString() {
return '$runtimeType(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
}
} }
\ No newline at end of file
...@@ -16,12 +16,12 @@ import 'scroll_simulation.dart'; ...@@ -16,12 +16,12 @@ import 'scroll_simulation.dart';
export 'package:flutter/physics.dart' show Tolerance; export 'package:flutter/physics.dart' show Tolerance;
@immutable @immutable
abstract class ScrollPhysics { class ScrollPhysics {
const ScrollPhysics(this.parent); const ScrollPhysics({ this.parent });
final ScrollPhysics parent; final ScrollPhysics parent;
ScrollPhysics applyTo(ScrollPhysics parent); ScrollPhysics applyTo(ScrollPhysics parent) => new ScrollPhysics(parent: parent);
/// Used by [DragScrollActivity] and other user-driven activities to /// Used by [DragScrollActivity] and other user-driven activities to
/// convert an offset in logical pixels as provided by the [DragUpdateDetails] /// convert an offset in logical pixels as provided by the [DragUpdateDetails]
...@@ -172,7 +172,7 @@ abstract class ScrollPhysics { ...@@ -172,7 +172,7 @@ abstract class ScrollPhysics {
/// clamping behavior. /// clamping behavior.
class BouncingScrollPhysics extends ScrollPhysics { class BouncingScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that bounce back from the edge. /// Creates scroll physics that bounce back from the edge.
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent); const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent); BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent);
...@@ -251,7 +251,7 @@ class BouncingScrollPhysics extends ScrollPhysics { ...@@ -251,7 +251,7 @@ class BouncingScrollPhysics extends ScrollPhysics {
class ClampingScrollPhysics extends ScrollPhysics { class ClampingScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that prevent the scroll offset from exceeding the /// Creates scroll physics that prevent the scroll offset from exceeding the
/// bounds of the content.. /// bounds of the content..
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent); const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent); ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent);
...@@ -325,13 +325,15 @@ class ClampingScrollPhysics extends ScrollPhysics { ...@@ -325,13 +325,15 @@ class ClampingScrollPhysics extends ScrollPhysics {
/// ///
/// See also: /// See also:
/// ///
/// * [ScrollPhysics], which can be used instead of this class when the default
/// behavior is desired instead.
/// * [BouncingScrollPhysics], which provides the bouncing overscroll behavior /// * [BouncingScrollPhysics], which provides the bouncing overscroll behavior
/// found on iOS. /// found on iOS.
/// * [ClampingScrollPhysics], which provides the clamping overscroll behavior /// * [ClampingScrollPhysics], which provides the clamping overscroll behavior
/// found on Android. /// found on Android.
class AlwaysScrollableScrollPhysics extends ScrollPhysics { class AlwaysScrollableScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that always lets the user scroll. /// Creates scroll physics that always lets the user scroll.
const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent); const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override @override
AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent); AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent);
......
...@@ -49,9 +49,10 @@ abstract class ScrollView extends StatelessWidget { ...@@ -49,9 +49,10 @@ abstract class ScrollView extends StatelessWidget {
this.reverse: false, this.reverse: false,
this.controller, this.controller,
bool primary, bool primary,
this.physics, ScrollPhysics physics,
this.shrinkWrap: false, this.shrinkWrap: false,
}) : primary = primary ?? controller == null && scrollDirection == Axis.vertical, }) : primary = primary ?? controller == null && scrollDirection == Axis.vertical,
physics = physics ?? (primary == true || (primary == null && controller == null && scrollDirection == Axis.vertical) ? const AlwaysScrollableScrollPhysics() : null),
super(key: key) { super(key: key) {
assert(reverse != null); assert(reverse != null);
assert(shrinkWrap != null); assert(shrinkWrap != null);
...@@ -90,7 +91,11 @@ abstract class ScrollView extends StatelessWidget { ...@@ -90,7 +91,11 @@ abstract class ScrollView extends StatelessWidget {
/// Whether this is the primary scroll view associated with the parent /// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController]. /// [PrimaryScrollController].
/// ///
/// On iOS, this identifies the scroll view that will scroll to top in /// When this is true, the scroll view is scrollable even if it does not have
/// sufficient content to actually scroll. Otherwise, by default the user can
/// only scroll the view if it has sufficient content. See [physics].
///
/// On iOS, this also identifies the scroll view that will scroll to top in
/// response to a tap in the status bar. /// response to a tap in the status bar.
/// ///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and /// Defaults to true when [scrollDirection] is [Axis.vertical] and
...@@ -102,7 +107,26 @@ abstract class ScrollView extends StatelessWidget { ...@@ -102,7 +107,26 @@ abstract class ScrollView extends StatelessWidget {
/// For example, determines how the scroll view continues to animate after the /// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view. /// user stops dragging the scroll view.
/// ///
/// Defaults to matching platform conventions. /// Defaults to matching platform conventions. Furthermore, if [primary] is
/// false, then the user cannot scroll if there is insufficient content to
/// scroll, while if [primary] is true, they can always attempt to scroll.
///
/// To force the scroll view to always be scrollable even if there is
/// insufficient content, as if [primary] was true but without necessarily
/// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics
/// object, as in:
///
/// ```dart
/// physics: const AlwaysScrollableScrollPhysics(),
/// ```
///
/// To force the scroll view to use the default platform conventions and not
/// be scrollable if there is insufficient content, regardless of the value of
/// [primary], provide an explicit [ScrollPhysics] object, as in:
///
/// ```dart
/// physics: const ScrollPhysics(),
/// ```
final ScrollPhysics physics; final ScrollPhysics physics;
/// Whether the extent of the scroll view in the [scrollDirection] should be /// Whether the extent of the scroll view in the [scrollDirection] should be
......
...@@ -48,6 +48,7 @@ void main() { ...@@ -48,6 +48,7 @@ void main() {
scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) { scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) {
return new ListView( return new ListView(
shrinkWrap: true, shrinkWrap: true,
primary: false,
children: <Widget>[ children: <Widget>[
new Container(height: 100.0, child: const Text('One')), new Container(height: 100.0, child: const Text('One')),
new Container(height: 100.0, child: const Text('Two')), new Container(height: 100.0, child: const Text('Two')),
......
...@@ -278,4 +278,86 @@ void main() { ...@@ -278,4 +278,86 @@ void main() {
); );
expect(innerScrollable.controller, isNull); expect(innerScrollable.controller, isNull);
}); });
testWidgets('Primary ListViews are always scrollable', (WidgetTester tester) async {
final ListView view = new ListView(primary: true);
expect(view.physics, const isInstanceOf<AlwaysScrollableScrollPhysics>());
});
testWidgets('Non-primary ListViews are not always scrollable', (WidgetTester tester) async {
final ListView view = new ListView(primary: false);
expect(view.physics, isNot(const isInstanceOf<AlwaysScrollableScrollPhysics>()));
});
testWidgets('Defaulting-to-primary ListViews are always scrollable', (WidgetTester tester) async {
final ListView view = new ListView(scrollDirection: Axis.vertical);
expect(view.physics, const isInstanceOf<AlwaysScrollableScrollPhysics>());
});
testWidgets('Defaulting-to-not-primary ListViews are not always scrollable', (WidgetTester tester) async {
final ListView view = new ListView(scrollDirection: Axis.horizontal);
expect(view.physics, isNot(const isInstanceOf<AlwaysScrollableScrollPhysics>()));
});
testWidgets('primary:true leads to scrolling', (WidgetTester tester) async {
bool scrolled = false;
await tester.pumpWidget(
new NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification message) { scrolled = true; return false; },
child: new ListView(
primary: true,
children: <Widget>[],
),
),
);
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
expect(scrolled, isTrue);
});
testWidgets('primary:false leads to no scrolling', (WidgetTester tester) async {
bool scrolled = false;
await tester.pumpWidget(
new NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification message) { scrolled = true; return false; },
child: new ListView(
primary: false,
children: <Widget>[],
),
),
);
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
expect(scrolled, isFalse);
});
testWidgets('physics:AlwaysScrollableScrollPhysics actually overrides primary:false default behaviour', (WidgetTester tester) async {
bool scrolled = false;
await tester.pumpWidget(
new NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification message) { scrolled = true; return false; },
child: new ListView(
primary: false,
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[],
),
),
);
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
expect(scrolled, isTrue);
});
testWidgets('physics:ScrollPhysics actually overrides primary:true default behaviour', (WidgetTester tester) async {
bool scrolled = false;
await tester.pumpWidget(
new NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification message) { scrolled = true; return false; },
child: new ListView(
primary: true,
physics: const ScrollPhysics(),
children: <Widget>[],
),
),
);
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
expect(scrolled, isFalse);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment