mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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
This commit is contained in:
parent
f739e9e022
commit
89856c0e5b
@ -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 {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
/// 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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
final AxisDirection axisDirection;
|
final AxisDirection axisDirection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '$runtimeType(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
///
|
///
|
||||||
/// 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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
/// 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() {
|
|||||||
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() {
|
|||||||
);
|
);
|
||||||
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user