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> {
),
child: new ListView(
shrinkWrap: true,
primary: false,
children: <Widget>[
new ListTile(
dense: true,
......
......@@ -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 {
/// These physics cause the page view to snap to page boundaries.
class PageScrollPhysics extends ScrollPhysics {
/// Creates physics for a [PageView].
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent);
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent);
......
......@@ -88,11 +88,6 @@ abstract class ScrollMetrics {
/// of the viewport in the scrollable. This is the content below the content
/// described by [extentInside].
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.
......@@ -131,4 +126,9 @@ class FixedScrollMetrics extends ScrollMetrics {
@override
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';
export 'package:flutter/physics.dart' show Tolerance;
@immutable
abstract class ScrollPhysics {
const ScrollPhysics(this.parent);
class ScrollPhysics {
const ScrollPhysics({ this.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
/// convert an offset in logical pixels as provided by the [DragUpdateDetails]
......@@ -172,7 +172,7 @@ abstract class ScrollPhysics {
/// clamping behavior.
class BouncingScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that bounce back from the edge.
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent);
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent);
......@@ -251,7 +251,7 @@ class BouncingScrollPhysics extends ScrollPhysics {
class ClampingScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that prevent the scroll offset from exceeding the
/// bounds of the content..
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent);
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent);
......@@ -325,13 +325,15 @@ class ClampingScrollPhysics extends ScrollPhysics {
///
/// 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
/// found on iOS.
/// * [ClampingScrollPhysics], which provides the clamping overscroll behavior
/// found on Android.
class AlwaysScrollableScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that always lets the user scroll.
const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent);
const AlwaysScrollableScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
AlwaysScrollableScrollPhysics applyTo(ScrollPhysics parent) => new AlwaysScrollableScrollPhysics(parent: parent);
......
......@@ -49,9 +49,10 @@ abstract class ScrollView extends StatelessWidget {
this.reverse: false,
this.controller,
bool primary,
this.physics,
ScrollPhysics physics,
this.shrinkWrap: false,
}) : primary = primary ?? controller == null && scrollDirection == Axis.vertical,
physics = physics ?? (primary == true || (primary == null && controller == null && scrollDirection == Axis.vertical) ? const AlwaysScrollableScrollPhysics() : null),
super(key: key) {
assert(reverse != null);
assert(shrinkWrap != null);
......@@ -90,7 +91,11 @@ abstract class ScrollView extends StatelessWidget {
/// Whether this is the primary scroll view associated with the parent
/// [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.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
......@@ -102,7 +107,26 @@ abstract class ScrollView extends StatelessWidget {
/// For example, determines how the scroll view continues to animate after the
/// 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;
/// Whether the extent of the scroll view in the [scrollDirection] should be
......
......@@ -48,6 +48,7 @@ void main() {
scaffoldKey.currentState.showBottomSheet<Null>((BuildContext context) {
return new ListView(
shrinkWrap: true,
primary: false,
children: <Widget>[
new Container(height: 100.0, child: const Text('One')),
new Container(height: 100.0, child: const Text('Two')),
......
......@@ -278,4 +278,86 @@ void main() {
);
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