sliver_persistent_header.dart 13.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1 2 3 4 5 6 7 8 9
// 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';

10
/// Delegate for configuring a [SliverPersistentHeader].
11
abstract class SliverPersistentHeaderDelegate {
Ian Hickson's avatar
Ian Hickson committed
12 13
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
14
  const SliverPersistentHeaderDelegate();
Ian Hickson's avatar
Ian Hickson committed
15

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
  /// 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`.
36 37
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent);

38 39 40 41 42 43 44 45 46
  /// 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.
47
  double get minExtent;
Ian Hickson's avatar
Ian Hickson committed
48

49 50 51 52 53 54 55 56 57
  /// 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.
Ian Hickson's avatar
Ian Hickson committed
58 59
  double get maxExtent;

60 61 62 63
  /// 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.
64 65 66 67 68
  ///
  /// This is only used for floating headers (those with
  /// [SliverPersistentHeader.floating] set to true).
  ///
  /// Defaults to null.
69
  FloatingHeaderSnapConfiguration get snapConfiguration => null;
70 71 72 73 74 75 76 77 78 79 80

  /// 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);
Ian Hickson's avatar
Ian Hickson committed
81 82
}

83 84 85 86 87
/// 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.
88
class SliverPersistentHeader extends StatelessWidget {
89 90 91 92
  /// 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.
93
  const SliverPersistentHeader({
Ian Hickson's avatar
Ian Hickson committed
94 95 96 97
    Key key,
    @required this.delegate,
    this.pinned: false,
    this.floating: false,
98 99 100 101
  }) : assert(delegate != null),
       assert(pinned != null),
       assert(floating != null),
       super(key: key);
Ian Hickson's avatar
Ian Hickson committed
102

103 104 105 106 107 108 109 110 111
  /// 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.
112
  final SliverPersistentHeaderDelegate delegate;
Ian Hickson's avatar
Ian Hickson committed
113

114 115 116 117 118
  /// 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.
Ian Hickson's avatar
Ian Hickson committed
119 120
  final bool pinned;

121 122 123 124 125 126 127 128
  /// 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.
Ian Hickson's avatar
Ian Hickson committed
129 130 131 132
  final bool floating;

  @override
  Widget build(BuildContext context) {
133 134
    if (floating && pinned)
      return new _SliverFloatingPinnedPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
135
    if (pinned)
136
      return new _SliverPinnedPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
137
    if (floating)
138 139
      return new _SliverFloatingPersistentHeader(delegate: delegate);
    return new _SliverScrollingPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
140 141 142
  }

  @override
143
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
144 145
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate', delegate));
146
    final List<String> flags = <String>[];
Ian Hickson's avatar
Ian Hickson committed
147 148 149 150 151 152
    if (pinned)
      flags.add('pinned');
    if (floating)
      flags.add('floating');
    if (flags.isEmpty)
      flags.add('normal');
153
    description.add(new IterableProperty<String>('mode', flags));
Ian Hickson's avatar
Ian Hickson committed
154 155 156
  }
}

157 158
class _SliverPersistentHeaderElement extends RenderObjectElement {
  _SliverPersistentHeaderElement(_SliverPersistentHeaderRenderObjectWidget widget) : super(widget);
Ian Hickson's avatar
Ian Hickson committed
159 160

  @override
161
  _SliverPersistentHeaderRenderObjectWidget get widget => super.widget;
Ian Hickson's avatar
Ian Hickson committed
162 163

  @override
164
  _RenderSliverPersistentHeaderForWidgetsMixin get renderObject => super.renderObject;
Ian Hickson's avatar
Ian Hickson committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    renderObject._element = this;
  }

  @override
  void unmount() {
    super.unmount();
    renderObject._element = null;
  }

  @override
179 180
  void update(_SliverPersistentHeaderRenderObjectWidget newWidget) {
    final _SliverPersistentHeaderRenderObjectWidget oldWidget = widget;
Ian Hickson's avatar
Ian Hickson committed
181
    super.update(newWidget);
182 183
    final SliverPersistentHeaderDelegate newDelegate = newWidget.delegate;
    final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate;
Ian Hickson's avatar
Ian Hickson committed
184 185 186 187 188 189 190 191 192 193 194 195
    if (newDelegate != oldDelegate &&
        (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
      renderObject.triggerRebuild();
  }

  @override
  void performRebuild() {
    renderObject.triggerRebuild();
  }

  Element child;

196
  void _build(double shrinkOffset, bool overlapsContent) {
Ian Hickson's avatar
Ian Hickson committed
197
    owner.buildScope(this, () {
198
      child = updateChild(child, widget.delegate.build(this, shrinkOffset, overlapsContent), null);
Ian Hickson's avatar
Ian Hickson committed
199 200 201 202 203 204 205 206 207 208
    });
  }

  @override
  void forgetChild(Element child) {
    assert(child == this.child);
    this.child = null;
  }

  @override
209
  void insertChildRenderObject(covariant RenderObject child, Null slot) {
210
    assert(renderObject.debugValidateChild(child));
Ian Hickson's avatar
Ian Hickson committed
211 212 213 214
    renderObject.child = child;
  }

  @override
215
  void moveChildRenderObject(covariant RenderObject child, Null slot) {
Ian Hickson's avatar
Ian Hickson committed
216 217 218 219
    assert(false);
  }

  @override
220
  void removeChildRenderObject(covariant RenderObject child) {
Ian Hickson's avatar
Ian Hickson committed
221 222 223 224 225
    renderObject.child = null;
  }

  @override
  void visitChildren(ElementVisitor visitor) {
226 227
    if (child != null)
      visitor(child);
Ian Hickson's avatar
Ian Hickson committed
228 229 230
  }
}

231
abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWidget {
232
  const _SliverPersistentHeaderRenderObjectWidget({
Ian Hickson's avatar
Ian Hickson committed
233 234
    Key key,
    @required this.delegate,
235 236
  }) : assert(delegate != null),
       super(key: key);
Ian Hickson's avatar
Ian Hickson committed
237

238
  final SliverPersistentHeaderDelegate delegate;
Ian Hickson's avatar
Ian Hickson committed
239 240

  @override
241
  _SliverPersistentHeaderElement createElement() => new _SliverPersistentHeaderElement(this);
Ian Hickson's avatar
Ian Hickson committed
242 243

  @override
244
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context);
Ian Hickson's avatar
Ian Hickson committed
245 246

  @override
247
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
248 249
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate', delegate));
Ian Hickson's avatar
Ian Hickson committed
250 251 252
  }
}

253 254 255 256 257
abstract class _RenderSliverPersistentHeaderForWidgetsMixin extends RenderSliverPersistentHeader {
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory _RenderSliverPersistentHeaderForWidgetsMixin._() => null;

258
  _SliverPersistentHeaderElement _element;
Ian Hickson's avatar
Ian Hickson committed
259

260 261 262
  @override
  double get minExtent => _element.widget.delegate.minExtent;

Ian Hickson's avatar
Ian Hickson committed
263 264 265 266
  @override
  double get maxExtent => _element.widget.delegate.maxExtent;

  @override
267
  void updateChild(double shrinkOffset, bool overlapsContent) {
Ian Hickson's avatar
Ian Hickson committed
268
    assert(_element != null);
269
    _element._build(shrinkOffset, overlapsContent);
Ian Hickson's avatar
Ian Hickson committed
270 271 272 273
  }

  @protected
  void triggerRebuild() {
274
    markNeedsLayout();
Ian Hickson's avatar
Ian Hickson committed
275 276 277
  }
}

278
class _SliverScrollingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
279
  const _SliverScrollingPersistentHeader({
Ian Hickson's avatar
Ian Hickson committed
280
    Key key,
281
    @required SliverPersistentHeaderDelegate delegate,
Ian Hickson's avatar
Ian Hickson committed
282 283 284
  }) : super(key: key, delegate: delegate);

  @override
285 286
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
    return new _RenderSliverScrollingPersistentHeaderForWidgets();
Ian Hickson's avatar
Ian Hickson committed
287 288 289 290
  }
}

// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
291
abstract class _RenderSliverScrollingPersistentHeader extends RenderSliverScrollingPersistentHeader { }
Ian Hickson's avatar
Ian Hickson committed
292

293 294
class _RenderSliverScrollingPersistentHeaderForWidgets extends _RenderSliverScrollingPersistentHeader
  with _RenderSliverPersistentHeaderForWidgetsMixin { }
Ian Hickson's avatar
Ian Hickson committed
295

296
class _SliverPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
297
  const _SliverPinnedPersistentHeader({
Ian Hickson's avatar
Ian Hickson committed
298
    Key key,
299
    @required SliverPersistentHeaderDelegate delegate,
Ian Hickson's avatar
Ian Hickson committed
300 301 302
  }) : super(key: key, delegate: delegate);

  @override
303 304
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
    return new _RenderSliverPinnedPersistentHeaderForWidgets();
Ian Hickson's avatar
Ian Hickson committed
305 306 307 308
  }
}

// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
309
abstract class _RenderSliverPinnedPersistentHeader extends RenderSliverPinnedPersistentHeader { }
Ian Hickson's avatar
Ian Hickson committed
310

311
class _RenderSliverPinnedPersistentHeaderForWidgets extends _RenderSliverPinnedPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }
Ian Hickson's avatar
Ian Hickson committed
312

313
class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
314
  const _SliverFloatingPersistentHeader({
Ian Hickson's avatar
Ian Hickson committed
315
    Key key,
316
    @required SliverPersistentHeaderDelegate delegate,
Ian Hickson's avatar
Ian Hickson committed
317 318 319
  }) : super(key: key, delegate: delegate);

  @override
320
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
321 322 323 324 325 326 327 328 329
    // 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;
Ian Hickson's avatar
Ian Hickson committed
330 331 332
  }
}

333 334 335 336 337 338
// 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 {
339
  const _SliverFloatingPinnedPersistentHeader({
340 341 342 343 344 345
    Key key,
    @required SliverPersistentHeaderDelegate delegate,
  }) : super(key: key, delegate: delegate);

  @override
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
346 347 348 349 350 351 352 353 354
    // 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;
355 356 357
  }
}

Ian Hickson's avatar
Ian Hickson committed
358
// This class exists to work around https://github.com/dart-lang/sdk/issues/15101
359
abstract class _RenderSliverFloatingPersistentHeader extends RenderSliverFloatingPersistentHeader { }
Ian Hickson's avatar
Ian Hickson committed
360

361
class _RenderSliverFloatingPersistentHeaderForWidgets extends _RenderSliverFloatingPersistentHeader with _RenderSliverPersistentHeaderForWidgetsMixin { }