| // Copyright 2014 The Flutter 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 'dart:math' as math; |
| |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void verifyPaintPosition(GlobalKey key, Offset ideal) { |
| final RenderObject target = key.currentContext!.findRenderObject()!; |
| expect(target.parent, isA<RenderViewport>()); |
| final SliverPhysicalParentData parentData = target.parentData! as SliverPhysicalParentData; |
| final Offset actual = parentData.paintOffset; |
| expect(actual, ideal); |
| } |
| |
| void main() { |
| testWidgets('Sliver protocol', (WidgetTester tester) async { |
| GlobalKey key1, key2, key3, key4, key5; |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: CustomScrollView( |
| slivers: <Widget>[ |
| BigSliver(key: key1 = GlobalKey()), |
| OverlappingSliver(key: key2 = GlobalKey()), |
| OverlappingSliver(key: key3 = GlobalKey()), |
| BigSliver(key: key4 = GlobalKey()), |
| BigSliver(key: key5 = GlobalKey()), |
| ], |
| ), |
| ), |
| ); |
| final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; |
| const double max = RenderBigSliver.height * 3.0 + (RenderOverlappingSliver.totalHeight) * 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, Offset.zero); |
| verifyPaintPosition(key2, Offset.zero); |
| verifyPaintPosition(key3, Offset.zero); |
| verifyPaintPosition(key4, Offset.zero); |
| verifyPaintPosition(key5, const Offset(0.0, 50.0)); |
| }); |
| } |
| |
| 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(); |
| } |
| } |
| |
| class RenderOverlappingSliver extends RenderSliver { |
| static const double totalHeight = 200.0; |
| static const double fixedHeight = 100.0; |
| |
| double get paintExtent { |
| return math.min( |
| math.max( |
| fixedHeight, |
| totalHeight - constraints.scrollOffset, |
| ), |
| constraints.remainingPaintExtent, |
| ); |
| } |
| |
| double get layoutExtent { |
| return (totalHeight - constraints.scrollOffset).clamp(0.0, constraints.remainingPaintExtent); |
| } |
| |
| @override |
| void performLayout() { |
| geometry = SliverGeometry( |
| scrollExtent: totalHeight, |
| paintExtent: paintExtent, |
| layoutExtent: layoutExtent, |
| maxPaintExtent: totalHeight, |
| ); |
| } |
| } |
| |
| class OverlappingSliver extends LeafRenderObjectWidget { |
| const OverlappingSliver({ Key? key }) : super(key: key); |
| @override |
| RenderOverlappingSliver createRenderObject(BuildContext context) { |
| return RenderOverlappingSliver(); |
| } |
| } |