1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | |
6 | /// @docImport 'notification_listener.dart'; |
7 | /// @docImport 'scroll_view.dart'; |
8 | /// @docImport 'sliver_floating_header.dart'; |
9 | /// @docImport 'sliver_persistent_header.dart'; |
10 | /// @docImport 'sliver_resizing_header.dart'; |
11 | library; |
12 | |
13 | import 'dart:math' as math; |
14 | |
15 | import 'package:flutter/foundation.dart'; |
16 | import 'package:flutter/rendering.dart'; |
17 | |
18 | import 'framework.dart'; |
19 | |
20 | /// A sliver that keeps its Widget child at the top of the a [CustomScrollView]. |
21 | /// |
22 | /// This sliver is preferable to the general purpose [SliverPersistentHeader] |
23 | /// for its relatively narrow use case because there's no need to create a |
24 | /// [SliverPersistentHeaderDelegate] or to predict the header's size. |
25 | /// |
26 | /// {@tool dartpad} |
27 | /// This example demonstrates that the sliver's size can change. Pressing the |
28 | /// floating action button replaces the one line of header text with two lines. |
29 | /// |
30 | /// ** See code in examples/api/lib/widgets/sliver/pinned_header_sliver.0.dart ** |
31 | /// {@end-tool} |
32 | /// |
33 | /// {@tool dartpad} |
34 | /// A more elaborate example which creates an app bar that's similar to the one |
35 | /// that appears in the iOS Settings app. In this example the pinned header |
36 | /// starts out transparent and the first item in the list serves as the app's |
37 | /// "Settings" title. When the title item has been scrolled completely behind |
38 | /// the pinned header, the header animates its opacity from 0 to 1 and its |
39 | /// (centered) "Settings" title appears. The fact that the header's opacity |
40 | /// depends on the height of the title item - which is unknown until the list |
41 | /// has been laid out - necessitates monitoring the title item's |
42 | /// [SliverGeometry.scrollExtent] and the header's [SliverConstraints.scrollOffset] |
43 | /// from a scroll [NotificationListener]. See the source code for more details. |
44 | /// |
45 | /// ** See code in examples/api/lib/widgets/sliver/pinned_header_sliver.1.dart ** |
46 | /// {@end-tool} |
47 | /// |
48 | /// See also: |
49 | /// |
50 | /// * [SliverResizingHeader] - which similarly pins the header at the top |
51 | /// of the [CustomScrollView] but reacts to scrolling by resizing the header |
52 | /// between its minimum and maximum extent limits. |
53 | /// * [SliverFloatingHeader] - which animates the header in and out of view |
54 | /// in response to downward and upwards scrolls. |
55 | /// * [SliverPersistentHeader] - a general purpose header that can be |
56 | /// configured as a pinned, resizing, or floating header. |
57 | class PinnedHeaderSliver extends SingleChildRenderObjectWidget { |
58 | /// Creates a sliver whose [Widget] child appears at the top of a |
59 | /// [CustomScrollView]. |
60 | const PinnedHeaderSliver({ |
61 | super.key, |
62 | super.child, |
63 | }); |
64 | |
65 | @override |
66 | RenderObject createRenderObject(BuildContext context) { |
67 | return _RenderPinnedHeaderSliver(); |
68 | } |
69 | } |
70 | |
71 | class _RenderPinnedHeaderSliver extends RenderSliverSingleBoxAdapter { |
72 | _RenderPinnedHeaderSliver(); |
73 | |
74 | double get childExtent { |
75 | if (child == null) { |
76 | return 0.0; |
77 | } |
78 | assert(child!.hasSize); |
79 | return switch (constraints.axis) { |
80 | Axis.vertical => child!.size.height, |
81 | Axis.horizontal => child!.size.width, |
82 | }; |
83 | } |
84 | |
85 | @override |
86 | double childMainAxisPosition(covariant RenderObject child) => 0; |
87 | |
88 | @override |
89 | void performLayout() { |
90 | final SliverConstraints constraints = this.constraints; |
91 | child?.layout(constraints.asBoxConstraints(), parentUsesSize: true); |
92 | |
93 | final double layoutExtent = clampDouble(childExtent - constraints.scrollOffset, 0, constraints.remainingPaintExtent); |
94 | final double paintExtent = math.min(childExtent, constraints.remainingPaintExtent - constraints.overlap); |
95 | geometry = SliverGeometry( |
96 | scrollExtent: childExtent, |
97 | paintOrigin: constraints.overlap, |
98 | paintExtent: paintExtent, |
99 | layoutExtent: layoutExtent, |
100 | maxPaintExtent: childExtent, |
101 | maxScrollObstructionExtent: childExtent, |
102 | cacheExtent: calculateCacheOffset(constraints, from: 0.0, to: childExtent), |
103 | hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. |
104 | ); |
105 | } |
106 | } |
107 | |