blob: 9ebee1081902979a2c47a8f4f3d0aec2fdad8300 [file] [log] [blame]
// Copyright 2015 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
/// Delegate for configuring a [SliverPersistentHeader].
abstract class SliverPersistentHeaderDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SliverPersistentHeaderDelegate();
/// The widget to place inside the [SliverPersistentHeader].
///
/// The `context` is the [BuildContext] of the sliver.
///
/// The `shrinkOffset` is a distance from [maxExtent] towards [minExtent]
/// representing the current amount by which the sliver has been shrunk. When
/// the `shrinkOffset` is zero, the contents will be rendered with a dimension
/// of [maxExtent] in the main axis. When `shrinkOffset` equals the difference
/// between [maxExtent] and [minExtent] (a positive number), the contents will
/// be rendered with a dimension of [minExtent] in the main axis. The
/// `shrinkOffset` will always be a positive number in that range.
///
/// The `overlapsContent` argument is true if subsequent slivers (if any) will
/// be rendered beneath this one, and false if the sliver will not have any
/// contents below it. Typically this is used to decide whether to draw a
/// shadow to simulate the sliver being above the contents below it. Typically
/// this is true when `shrinkOffset` is at its greatest value and false
/// otherwise, but that is not guaranteed. See [NestedScrollView] for an
/// example of a case where `overlapsContent`'s value can be unrelated to
/// `shrinkOffset`.
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent);
/// The smallest size to allow the header to reach, when it shrinks at the
/// start of the viewport.
///
/// This must return a value equal to or less than [maxExtent].
///
/// This value should not change over the lifetime of the delegate. It should
/// be based entirely on the constructor arguments passed to the delegate. See
/// [shouldRebuild], which must return true if a new delegate would return a
/// different value.
double get minExtent;
/// The size of the header when it is not shrinking at the top of the
/// viewport.
///
/// This must return a value equal to or greater than [minExtent].
///
/// This value should not change over the lifetime of the delegate. It should
/// be based entirely on the constructor arguments passed to the delegate. See
/// [shouldRebuild], which must return true if a new delegate would return a
/// different value.
double get maxExtent;
/// Specifies how floating headers should animate in and out of view.
///
/// If the value of this property is null, then floating headers will
/// not animate into place.
///
/// This is only used for floating headers (those with
/// [SliverPersistentHeader.floating] set to true).
///
/// Defaults to null.
FloatingHeaderSnapConfiguration get snapConfiguration => null;
/// Whether this delegate is meaningfully different from the old delegate.
///
/// If this returns false, then the header might not be rebuilt, even though
/// the instance of the delegate changed.
///
/// This must return true if `oldDelegate` and this object would return
/// different values for [minExtent], [maxExtent], [snapConfiguration], or
/// would return a meaningfully different widget tree from [build] for the
/// same arguments.
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate);
}
/// A sliver whose size varies when the sliver is scrolled to the leading edge
/// of the viewport.
///
/// This is the layout primitive that [SliverAppBar] uses for its
/// shrinking/growing effect.
class SliverPersistentHeader extends StatelessWidget {
/// Creates a sliver that varies its size when it is scrolled to the start of
/// a viewport.
///
/// The [delegate], [pinned], and [floating] arguments must not be null.
const SliverPersistentHeader({
Key key,
@required this.delegate,
this.pinned: false,
this.floating: false,
}) : assert(delegate != null),
assert(pinned != null),
assert(floating != null),
super(key: key);
/// Configuration for the sliver's layout.
///
/// The delegate provides the following information:
///
/// * The minimum and maximum dimensions of the sliver.
///
/// * The builder for generating the widgets of the sliver.
///
/// * The instructions for snapping the scroll offset, if [floating] is true.
final SliverPersistentHeaderDelegate delegate;
/// Whether to stick the header to the start of the viewport once it has
/// reached its minimum size.
///
/// If this is false, the header will continue scrolling off the screen after
/// it has shrunk to its minimum extent.
final bool pinned;
/// Whether the header should immediately grow again if the user reverses
/// scroll direction.
///
/// If this is false, the header only grows again once the user reaches the
/// part of the viewport that contains the sliver.
///
/// The [delegate]'s [SliverPersistentHeaderDelegate.snapConfiguration] is
/// ignored unless [floating] is true.
final bool floating;
@override
Widget build(BuildContext context) {
if (floating && pinned)
return new _SliverFloatingPinnedPersistentHeader(delegate: delegate);
if (pinned)
return new _SliverPinnedPersistentHeader(delegate: delegate);
if (floating)
return new _SliverFloatingPersistentHeader(delegate: delegate);
return new _SliverScrollingPersistentHeader(delegate: delegate);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate', delegate));
final List<String> flags = <String>[];
if (pinned)
flags.add('pinned');
if (floating)
flags.add('floating');
if (flags.isEmpty)
flags.add('normal');
properties.add(new IterableProperty<String>('mode', flags));
}
}
class _SliverPersistentHeaderElement extends RenderObjectElement {
_SliverPersistentHeaderElement(_SliverPersistentHeaderRenderObjectWidget widget) : super(widget);
@override
_SliverPersistentHeaderRenderObjectWidget get widget => super.widget;
@override
_RenderSliverPersistentHeaderForWidgetsMixin get renderObject => super.renderObject;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
renderObject._element = this;
}
@override
void unmount() {
super.unmount();
renderObject._element = null;
}
@override
void update(_SliverPersistentHeaderRenderObjectWidget newWidget) {
final _SliverPersistentHeaderRenderObjectWidget oldWidget = widget;
super.update(newWidget);
final SliverPersistentHeaderDelegate newDelegate = newWidget.delegate;
final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate &&
(newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
renderObject.triggerRebuild();
}
@override
void performRebuild() {
renderObject.triggerRebuild();
}
Element child;
void _build(double shrinkOffset, bool overlapsContent) {
owner.buildScope(this, () {
child = updateChild(child, widget.delegate.build(this, shrinkOffset, overlapsContent), null);
});
}
@override
void forgetChild(Element child) {
assert(child == this.child);
this.child = null;
}
@override
void insertChildRenderObject(covariant RenderObject child, Null slot) {
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
}
@override
void moveChildRenderObject(covariant RenderObject child, Null slot) {
assert(false);
}
@override
void removeChildRenderObject(covariant RenderObject child) {
renderObject.child = null;
}
@override
void visitChildren(ElementVisitor visitor) {
if (child != null)
visitor(child);
}
}
abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWidget {
const _SliverPersistentHeaderRenderObjectWidget({
Key key,
@required this.delegate,
}) : assert(delegate != null),
super(key: key);
final SliverPersistentHeaderDelegate delegate;
@override
_SliverPersistentHeaderElement createElement() => new _SliverPersistentHeaderElement(this);
@override
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context);
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate', delegate));
}
}
abstract class _RenderSliverPersistentHeaderForWidgetsMixin extends RenderSliverPersistentHeader {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory _RenderSliverPersistentHeaderForWidgetsMixin._() => null;
_SliverPersistentHeaderElement _element;
@override
double get minExtent => _element.widget.delegate.minExtent;
@override
double get maxExtent => _element.widget.delegate.maxExtent;
@override
void updateChild(double shrinkOffset, bool overlapsContent) {
assert(_element != null);
_element._build(shrinkOffset, overlapsContent);
}
@protected
void triggerRebuild() {
markNeedsLayout();
}
}
class _SliverScrollingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
const _SliverScrollingPersistentHeader({
Key key,
@required SliverPersistentHeaderDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
return new _RenderSliverScrollingPersistentHeaderForWidgets();
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverScrollingPersistentHeader extends RenderSliverScrollingPersistentHeader { }
class _RenderSliverScrollingPersistentHeaderForWidgets extends _RenderSliverScrollingPersistentHeader
with _RenderSliverPersistentHeaderForWidgetsMixin { }
class _SliverPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
const _SliverPinnedPersistentHeader({
Key key,
@required SliverPersistentHeaderDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
return new _RenderSliverPinnedPersistentHeaderForWidgets();
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverPinnedPersistentHeader extends RenderSliverPinnedPersistentHeader { }
class _RenderSliverPinnedPersistentHeaderForWidgets extends _RenderSliverPinnedPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }
class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
const _SliverFloatingPersistentHeader({
Key key,
@required SliverPersistentHeaderDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
// Not passing this snapConfiguration as a constructor parameter to avoid the
// additional layers added due to https://github.com/dart-lang/sdk/issues/15101
return new _RenderSliverFloatingPersistentHeaderForWidgets()
..snapConfiguration = delegate.snapConfiguration;
}
@override
void updateRenderObject(BuildContext context, _RenderSliverFloatingPersistentHeaderForWidgets renderObject) {
renderObject.snapConfiguration = delegate.snapConfiguration;
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPinnedPersistentHeader { }
class _RenderSliverFloatingPinnedPersistentHeaderForWidgets extends _RenderSliverFloatingPinnedPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }
class _SliverFloatingPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
const _SliverFloatingPinnedPersistentHeader({
Key key,
@required SliverPersistentHeaderDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
_RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
// Not passing this snapConfiguration as a constructor parameter to avoid the
// additional layers added due to https://github.com/dart-lang/sdk/issues/15101
return new _RenderSliverFloatingPinnedPersistentHeaderForWidgets()
..snapConfiguration = delegate.snapConfiguration;
}
@override
void updateRenderObject(BuildContext context, _RenderSliverFloatingPinnedPersistentHeaderForWidgets renderObject) {
renderObject.snapConfiguration = delegate.snapConfiguration;
}
}
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
abstract class _RenderSliverFloatingPersistentHeader extends RenderSliverFloatingPersistentHeader { }
class _RenderSliverFloatingPersistentHeaderForWidgets extends _RenderSliverFloatingPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }