blob: bf305fe9ba730d9fb3385009d9fe7d35f3ae7c90 [file] [log] [blame]
// 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.
/// @docImport 'notification_listener.dart';
/// @docImport 'scroll_view.dart';
/// @docImport 'sliver_floating_header.dart';
/// @docImport 'sliver_persistent_header.dart';
/// @docImport 'sliver_resizing_header.dart';
library;
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
/// A sliver that keeps its Widget child at the top of the a [CustomScrollView].
///
/// This sliver is preferable to the general purpose [SliverPersistentHeader]
/// for its relatively narrow use case because there's no need to create a
/// [SliverPersistentHeaderDelegate] or to predict the header's size.
///
/// {@tool dartpad}
/// This example demonstrates that the sliver's size can change. Pressing the
/// floating action button replaces the one line of header text with two lines.
///
/// ** See code in examples/api/lib/widgets/sliver/pinned_header_sliver.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// A more elaborate example which creates an app bar that's similar to the one
/// that appears in the iOS Settings app. In this example the pinned header
/// starts out transparent and the first item in the list serves as the app's
/// "Settings" title. When the title item has been scrolled completely behind
/// the pinned header, the header animates its opacity from 0 to 1 and its
/// (centered) "Settings" title appears. The fact that the header's opacity
/// depends on the height of the title item - which is unknown until the list
/// has been laid out - necessitates monitoring the title item's
/// [SliverGeometry.scrollExtent] and the header's [SliverConstraints.scrollOffset]
/// from a scroll [NotificationListener]. See the source code for more details.
///
/// ** See code in examples/api/lib/widgets/sliver/pinned_header_sliver.1.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverResizingHeader] - which similarly pins the header at the top
/// of the [CustomScrollView] but reacts to scrolling by resizing the header
/// between its minimum and maximum extent limits.
/// * [SliverFloatingHeader] - which animates the header in and out of view
/// in response to downward and upwards scrolls.
/// * [SliverPersistentHeader] - a general purpose header that can be
/// configured as a pinned, resizing, or floating header.
class PinnedHeaderSliver extends SingleChildRenderObjectWidget {
/// Creates a sliver whose [Widget] child appears at the top of a
/// [CustomScrollView].
const PinnedHeaderSliver({super.key, super.child});
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderPinnedHeaderSliver();
}
}
class _RenderPinnedHeaderSliver extends RenderSliverSingleBoxAdapter {
_RenderPinnedHeaderSliver();
double get childExtent {
if (child == null) {
return 0.0;
}
assert(child!.hasSize);
return switch (constraints.axis) {
Axis.vertical => child!.size.height,
Axis.horizontal => child!.size.width,
};
}
@override
double childMainAxisPosition(covariant RenderObject child) => 0;
@override
void performLayout() {
final SliverConstraints constraints = this.constraints;
child?.layout(constraints.asBoxConstraints(), parentUsesSize: true);
final double layoutExtent = clampDouble(
childExtent - constraints.scrollOffset,
0,
constraints.remainingPaintExtent,
);
final double paintExtent = math.min(
childExtent,
constraints.remainingPaintExtent - constraints.overlap,
);
geometry = SliverGeometry(
scrollExtent: childExtent,
paintOrigin: constraints.overlap,
paintExtent: paintExtent,
layoutExtent: layoutExtent,
maxPaintExtent: childExtent,
maxScrollObstructionExtent: childExtent,
cacheExtent: calculateCacheOffset(constraints, from: 0.0, to: childExtent),
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
);
}
}