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