1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2016 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/rendering.dart';
import 'package:flutter/widgets.dart';
void verifyPaintPosition(GlobalKey key, Offset ideal) {
final RenderObject target = key.currentContext.findRenderObject();
expect(target.parent, isInstanceOf<RenderViewport>());
final SliverPhysicalParentData parentData = target.parentData;
final Offset actual = parentData.paintOffset;
expect(actual, ideal);
}
void main() {
testWidgets('Sliver appbars - scrolling', (WidgetTester tester) async {
GlobalKey key1, key2, key3, key4, key5;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
BigSliver(key: key1 = GlobalKey()),
SliverPersistentHeader(key: key2 = GlobalKey(), delegate: TestDelegate()),
SliverPersistentHeader(key: key3 = GlobalKey(), delegate: TestDelegate()),
BigSliver(key: key4 = GlobalKey()),
BigSliver(key: key5 = GlobalKey()),
],
),
),
);
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
final double max = RenderBigSliver.height * 3.0 + TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0);
expect(max, 1450.0);
expect(position.pixels, 0.0);
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, max);
position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 10));
expect(position.pixels, max);
expect(position.minScrollExtent, 0.0);
expect(position.maxScrollExtent, max);
verifyPaintPosition(key1, const Offset(0.0, 0.0));
verifyPaintPosition(key2, const Offset(0.0, 0.0));
verifyPaintPosition(key3, const Offset(0.0, 0.0));
verifyPaintPosition(key4, const Offset(0.0, 0.0));
verifyPaintPosition(key5, const Offset(0.0, 50.0));
});
testWidgets('Sliver appbars - scrolling off screen', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final TestDelegate delegate = TestDelegate();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
slivers: <Widget>[
const BigSliver(),
SliverPersistentHeader(key: key, delegate: delegate),
const BigSliver(),
const BigSliver(),
],
),
),
);
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.animateTo(RenderBigSliver.height + delegate.maxExtent - 5.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpAndSettle(const Duration(milliseconds: 1000));
final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container));
final Rect rect = Rect.fromPoints(box.localToGlobal(Offset.zero), box.localToGlobal(box.size.bottomRight(Offset.zero)));
expect(rect, equals(Rect.fromLTWH(0.0, -195.0, 800.0, 200.0)));
});
testWidgets('Sliver appbars - scrolling - overscroll gap is below header', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverPersistentHeader(delegate: TestDelegate()),
const SliverList(
delegate: SliverChildListDelegate(<Widget>[
SizedBox(
height: 300.0,
child: Text('X'),
),
]),
),
],
),
),
);
expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 200.0));
final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position;
position.jumpTo(-50.0);
await tester.pump();
expect(tester.getTopLeft(find.byType(Container)), Offset.zero);
expect(tester.getTopLeft(find.text('X')), const Offset(0.0, 250.0));
});
}
class TestDelegate extends SliverPersistentHeaderDelegate {
@override
double get maxExtent => 200.0;
@override
double get minExtent => 200.0;
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(height: maxExtent);
}
@override
bool shouldRebuild(TestDelegate oldDelegate) => false;
}
class RenderBigSliver extends RenderSliver {
static const double height = 550.0;
double get paintExtent => (height - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent);
@override
void performLayout() {
geometry = SliverGeometry(
scrollExtent: height,
paintExtent: paintExtent,
maxPaintExtent: height,
);
}
}
class BigSliver extends LeafRenderObjectWidget {
const BigSliver({ Key key }) : super(key: key);
@override
RenderBigSliver createRenderObject(BuildContext context) {
return RenderBigSliver();
}
}