[H] Expose "center" on CustomScrollView (#27424)

* Expose "center" on CustomScrollView

* Also support anchor
This commit is contained in:
Ian Hickson 2019-02-02 16:23:39 -08:00 committed by GitHub
parent 16d3847847
commit 496ddc580c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 145 additions and 2 deletions

View file

@ -51,6 +51,12 @@ abstract class ScrollView extends StatelessWidget {
/// Creates a widget that scrolls.
///
/// If the [primary] argument is true, the [controller] must be null.
///
/// If the [shrinkWrap] argument is true, the [center] argument must be null.
///
/// The [scrollDirection], [reverse], and [shrinkWrap] arguments must not be null.
///
/// The [anchor] argument must be non-null and in the range 0.0 to 1.0.
const ScrollView({
Key key,
this.scrollDirection = Axis.vertical,
@ -59,16 +65,22 @@ abstract class ScrollView extends StatelessWidget {
bool primary,
ScrollPhysics physics,
this.shrinkWrap = false,
this.center,
this.anchor = 0.0,
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.down,
}) : assert(reverse != null),
}) : assert(scrollDirection != null),
assert(reverse != null),
assert(shrinkWrap != null),
assert(dragStartBehavior != null),
assert(!(controller != null && primary == true),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.'
),
assert(!shrinkWrap || center == null),
assert(anchor != null),
assert(anchor >= 0.0 && anchor <= 1.0),
primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
physics = physics ?? (primary == true || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null),
super(key: key);
@ -172,6 +184,32 @@ abstract class ScrollView extends StatelessWidget {
/// Defaults to false.
final bool shrinkWrap;
/// The first child in the [GrowthDirection.forward] growth direction.
///
/// Children after [center] will be placed in the [axisDirection] relative to
/// the [center]. Children before [center] will be placed in the opposite of
/// the [axisDirection] relative to the [center].
///
/// The [center] must be the key of one of the slivers built by [buildSlivers].
///
/// Of the built-in subclasses of [ScrollView], only [CustomScrollView]
/// supports [center]; for that class, the given key must be the key of one of
/// the slivers in the [CustomScrollView.slivers] list.
///
/// See also:
///
/// * [anchor], which controls where the [center] as aligned in the viewport.
final Key center;
/// The relative position of the zero scroll offset.
///
/// For example, if [anchor] is 0.5 and the [axisDirection] is
/// [AxisDirection.down] or [AxisDirection.up], then the zero scroll offset is
/// vertically centered within the viewport. If the [anchor] is 1.0, and the
/// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
/// on the left edge of the viewport.
final double anchor;
/// {@macro flutter.rendering.viewport.cacheExtent}
final double cacheExtent;
@ -221,6 +259,14 @@ abstract class ScrollView extends StatelessWidget {
/// Subclasses may override this method to change how the viewport is built.
/// The default implementation uses a [ShrinkWrappingViewport] if [shrinkWrap]
/// is true, and a regular [Viewport] otherwise.
///
/// The `offset` argument is the value obtained from
/// [Scrollable.viewportBuilder].
///
/// The `axisDirection` argument is the value obtained from [getDirection],
/// which by default uses [scrollDirection] and [reverse].
///
/// The `slivers` argument is the value obtained from [buildSlivers].
@protected
Widget buildViewport(
BuildContext context,
@ -240,6 +286,8 @@ abstract class ScrollView extends StatelessWidget {
offset: offset,
slivers: slivers,
cacheExtent: cacheExtent,
center: center,
anchor: anchor,
);
}
@ -392,7 +440,7 @@ abstract class ScrollView extends StatelessWidget {
class CustomScrollView extends ScrollView {
/// Creates a [ScrollView] that creates custom scroll effects using slivers.
///
/// If the [primary] argument is true, the [controller] must be null.
/// See the [new ScrollView] constructor for more details on these arguments.
const CustomScrollView({
Key key,
Axis scrollDirection = Axis.vertical,
@ -401,6 +449,8 @@ class CustomScrollView extends ScrollView {
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
Key center,
double anchor = 0.0,
double cacheExtent,
this.slivers = const <Widget>[],
int semanticChildCount,
@ -413,6 +463,8 @@ class CustomScrollView extends ScrollView {
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
center: center,
anchor: anchor,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount,
dragStartBehavior: dragStartBehavior,

View file

@ -0,0 +1,91 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('CustomScrollView.center', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('b'), child: SizedBox(height: 100.0)),
],
center: Key('a'),
),
));
await tester.pumpAndSettle();
expect(tester.getRect(find.descendant(of: find.byKey(const Key('a')), matching: find.byType(SizedBox))),
Rect.fromLTRB(0.0, 0.0, 800.0, 100.0));
expect(tester.getRect(find.descendant(of: find.byKey(const Key('b')), matching: find.byType(SizedBox))),
Rect.fromLTRB(0.0, 100.0, 800.0, 200.0));
});
testWidgets('CustomScrollView.center', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('b'), child: SizedBox(height: 100.0)),
],
center: Key('b'),
),
));
await tester.pumpAndSettle();
expect(
tester.getRect(
find.descendant(
of: find.byKey(const Key('a'), skipOffstage: false),
matching: find.byType(SizedBox, skipOffstage: false),
),
),
Rect.fromLTRB(0.0, -100.0, 800.0, 0.0),
);
expect(
tester.getRect(
find.descendant(
of: find.byKey(const Key('b')),
matching: find.byType(SizedBox),
),
),
Rect.fromLTRB(0.0, 0.0, 800.0, 100.0),
);
});
testWidgets('CustomScrollView.anchor', (WidgetTester tester) async {
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(key: Key('a'), child: SizedBox(height: 100.0)),
SliverToBoxAdapter(key: Key('b'), child: SizedBox(height: 100.0)),
],
center: Key('b'),
anchor: 1.0,
),
));
await tester.pumpAndSettle();
expect(
tester.getRect(
find.descendant(
of: find.byKey(const Key('a')),
matching: find.byType(SizedBox),
),
),
Rect.fromLTRB(0.0, 500.0, 800.0, 600.0),
);
expect(
tester.getRect(
find.descendant(
of: find.byKey(const Key('b'), skipOffstage: false),
matching: find.byType(SizedBox, skipOffstage: false),
),
),
Rect.fromLTRB(0.0, 600.0, 800.0, 700.0),
);
});
}