sliver_persistent_header.dart 16 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4 5 6
// 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';
7
import 'package:flutter/scheduler.dart' show TickerProvider;
Ian Hickson's avatar
Ian Hickson committed
8 9 10

import 'framework.dart';

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

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

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

50 51 52 53 54 55 56 57 58
  /// 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
59 60
  double get maxExtent;

61 62 63 64
  /// A [TickerProvider] to use when animating the header's size changes.
  ///
  /// Must not be null if the persistent header is a floating header, and
  /// [snapConfiguration] or [showOnScreenConfiguration] is not null.
65
  TickerProvider? get vsync => null;
66

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

78 79 80 81 82 83 84 85 86
  /// Specifies an [AsyncCallback] and offset for execution.
  ///
  /// If the value of this property is null, then callback will not be
  /// triggered.
  ///
  /// This is only used for stretching headers (those with
  /// [SliverAppBar.stretch] set to true).
  ///
  /// Defaults to null.
87
  OverScrollHeaderStretchConfiguration? get stretchConfiguration => null;
88

89 90 91 92
  /// Specifies how floating headers and pinned pinned headers should behave in
  /// response to [RenderObject.showOnScreen] calls.
  ///
  /// Defaults to null.
93
  PersistentHeaderShowOnScreenConfiguration? get showOnScreenConfiguration => null;
94

95 96 97 98 99 100 101 102 103 104
  /// 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
105 106
}

107 108 109 110 111
/// A sliver whose size varies when the sliver is scrolled to the edge
/// of the viewport opposite the sliver's [GrowthDirection].
///
/// In the normal case of a [CustomScrollView] with no centered sliver, this
/// sliver will vary its size when scrolled to the leading edge of the viewport.
112 113 114
///
/// This is the layout primitive that [SliverAppBar] uses for its
/// shrinking/growing effect.
115
class SliverPersistentHeader extends StatelessWidget {
116 117 118 119
  /// 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.
120
  const SliverPersistentHeader({
121 122
    Key? key,
    required this.delegate,
123 124
    this.pinned = false,
    this.floating = false,
125 126 127 128
  }) : assert(delegate != null),
       assert(pinned != null),
       assert(floating != null),
       super(key: key);
Ian Hickson's avatar
Ian Hickson committed
129

130 131 132 133 134 135 136 137 138
  /// 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.
139
  final SliverPersistentHeaderDelegate delegate;
Ian Hickson's avatar
Ian Hickson committed
140

141 142 143 144 145
  /// 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
146 147
  final bool pinned;

148 149 150 151 152 153 154 155
  /// 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
156 157 158 159
  final bool floating;

  @override
  Widget build(BuildContext context) {
160
    if (floating && pinned)
161
      return _SliverFloatingPinnedPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
162
    if (pinned)
163
      return _SliverPinnedPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
164
    if (floating)
165 166
      return _SliverFloatingPersistentHeader(delegate: delegate);
    return _SliverScrollingPersistentHeader(delegate: delegate);
Ian Hickson's avatar
Ian Hickson committed
167 168 169
  }

  @override
170 171
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
172 173 174 175 176 177
    properties.add(
      DiagnosticsProperty<SliverPersistentHeaderDelegate>(
        'delegate',
        delegate,
      )
    );
178 179 180 181
    final List<String> flags = <String>[
      if (pinned) 'pinned',
      if (floating) 'floating',
    ];
Ian Hickson's avatar
Ian Hickson committed
182 183
    if (flags.isEmpty)
      flags.add('normal');
184
    properties.add(IterableProperty<String>('mode', flags));
Ian Hickson's avatar
Ian Hickson committed
185 186 187
  }
}

188 189
class _SliverPersistentHeaderElement extends RenderObjectElement {
  _SliverPersistentHeaderElement(_SliverPersistentHeaderRenderObjectWidget widget) : super(widget);
Ian Hickson's avatar
Ian Hickson committed
190 191

  @override
192
  _SliverPersistentHeaderRenderObjectWidget get widget => super.widget as _SliverPersistentHeaderRenderObjectWidget;
Ian Hickson's avatar
Ian Hickson committed
193 194

  @override
195
  _RenderSliverPersistentHeaderForWidgetsMixin get renderObject => super.renderObject as _RenderSliverPersistentHeaderForWidgetsMixin;
Ian Hickson's avatar
Ian Hickson committed
196 197

  @override
198
  void mount(Element? parent, dynamic newSlot) {
Ian Hickson's avatar
Ian Hickson committed
199 200 201 202 203 204 205 206 207 208 209
    super.mount(parent, newSlot);
    renderObject._element = this;
  }

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

  @override
210 211
  void update(_SliverPersistentHeaderRenderObjectWidget newWidget) {
    final _SliverPersistentHeaderRenderObjectWidget oldWidget = widget;
Ian Hickson's avatar
Ian Hickson committed
212
    super.update(newWidget);
213 214
    final SliverPersistentHeaderDelegate newDelegate = newWidget.delegate;
    final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate;
Ian Hickson's avatar
Ian Hickson committed
215 216 217 218 219 220 221
    if (newDelegate != oldDelegate &&
        (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
      renderObject.triggerRebuild();
  }

  @override
  void performRebuild() {
222
    super.performRebuild();
Ian Hickson's avatar
Ian Hickson committed
223 224 225
    renderObject.triggerRebuild();
  }

226
  Element? child;
Ian Hickson's avatar
Ian Hickson committed
227

228
  void _build(double shrinkOffset, bool overlapsContent) {
229
    owner!.buildScope(this, () {
230 231 232 233 234 235 236 237 238
      child = updateChild(
        child,
        widget.delegate.build(
          this,
          shrinkOffset,
          overlapsContent
        ),
        null,
      );
Ian Hickson's avatar
Ian Hickson committed
239 240 241 242 243 244 245
    });
  }

  @override
  void forgetChild(Element child) {
    assert(child == this.child);
    this.child = null;
246
    super.forgetChild(child);
Ian Hickson's avatar
Ian Hickson committed
247 248 249
  }

  @override
250
  void insertRenderObjectChild(covariant RenderBox child, dynamic slot) {
251
    assert(renderObject.debugValidateChild(child));
Ian Hickson's avatar
Ian Hickson committed
252 253 254 255
    renderObject.child = child;
  }

  @override
256
  void moveRenderObjectChild(covariant RenderObject child, dynamic oldSlot, dynamic newSlot) {
Ian Hickson's avatar
Ian Hickson committed
257 258 259 260
    assert(false);
  }

  @override
261
  void removeRenderObjectChild(covariant RenderObject child, dynamic slot) {
Ian Hickson's avatar
Ian Hickson committed
262 263 264 265 266
    renderObject.child = null;
  }

  @override
  void visitChildren(ElementVisitor visitor) {
267
    if (child != null)
268
      visitor(child!);
Ian Hickson's avatar
Ian Hickson committed
269 270 271
  }
}

272
abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWidget {
273
  const _SliverPersistentHeaderRenderObjectWidget({
274 275
    Key? key,
    required this.delegate,
276 277
  }) : assert(delegate != null),
       super(key: key);
Ian Hickson's avatar
Ian Hickson committed
278

279
  final SliverPersistentHeaderDelegate delegate;
Ian Hickson's avatar
Ian Hickson committed
280 281

  @override
282
  _SliverPersistentHeaderElement createElement() => _SliverPersistentHeaderElement(this);
Ian Hickson's avatar
Ian Hickson committed
283 284

  @override
285
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context);
Ian Hickson's avatar
Ian Hickson committed
286 287

  @override
288
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
289
    super.debugFillProperties(description);
290 291 292 293 294 295
    description.add(
      DiagnosticsProperty<SliverPersistentHeaderDelegate>(
        'delegate',
        delegate,
      )
    );
Ian Hickson's avatar
Ian Hickson committed
296 297 298
  }
}

299
mixin _RenderSliverPersistentHeaderForWidgetsMixin on RenderSliverPersistentHeader {
300
  _SliverPersistentHeaderElement? _element;
Ian Hickson's avatar
Ian Hickson committed
301

302
  @override
303
  double get minExtent => _element!.widget.delegate.minExtent;
304

Ian Hickson's avatar
Ian Hickson committed
305
  @override
306
  double get maxExtent => _element!.widget.delegate.maxExtent;
Ian Hickson's avatar
Ian Hickson committed
307 308

  @override
309
  void updateChild(double shrinkOffset, bool overlapsContent) {
Ian Hickson's avatar
Ian Hickson committed
310
    assert(_element != null);
311
    _element!._build(shrinkOffset, overlapsContent);
Ian Hickson's avatar
Ian Hickson committed
312 313 314 315
  }

  @protected
  void triggerRebuild() {
316
    markNeedsLayout();
Ian Hickson's avatar
Ian Hickson committed
317 318 319
  }
}

320
class _SliverScrollingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
321
  const _SliverScrollingPersistentHeader({
322 323
    Key? key,
    required SliverPersistentHeaderDelegate delegate,
324 325 326 327
  }) : super(
    key: key,
    delegate: delegate,
  );
Ian Hickson's avatar
Ian Hickson committed
328 329

  @override
330
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
331 332 333
    return _RenderSliverScrollingPersistentHeaderForWidgets(
      stretchConfiguration: delegate.stretchConfiguration
    );
Ian Hickson's avatar
Ian Hickson committed
334 335 336
  }
}

337
class _RenderSliverScrollingPersistentHeaderForWidgets extends RenderSliverScrollingPersistentHeader
338 339
  with _RenderSliverPersistentHeaderForWidgetsMixin {
  _RenderSliverScrollingPersistentHeaderForWidgets({
340 341
    RenderBox? child,
    OverScrollHeaderStretchConfiguration? stretchConfiguration,
342 343 344 345 346
  }) : super(
    child: child,
    stretchConfiguration: stretchConfiguration,
  );
}
Ian Hickson's avatar
Ian Hickson committed
347

348
class _SliverPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
349
  const _SliverPinnedPersistentHeader({
350 351
    Key? key,
    required SliverPersistentHeaderDelegate delegate,
352 353 354 355
  }) : super(
    key: key,
    delegate: delegate,
  );
Ian Hickson's avatar
Ian Hickson committed
356 357

  @override
358
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
359
    return _RenderSliverPinnedPersistentHeaderForWidgets(
360 361
      stretchConfiguration: delegate.stretchConfiguration,
      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
362
    );
Ian Hickson's avatar
Ian Hickson committed
363 364 365
  }
}

366 367 368
class _RenderSliverPinnedPersistentHeaderForWidgets extends RenderSliverPinnedPersistentHeader
  with _RenderSliverPersistentHeaderForWidgetsMixin {
  _RenderSliverPinnedPersistentHeaderForWidgets({
369 370 371
    RenderBox? child,
    OverScrollHeaderStretchConfiguration? stretchConfiguration,
    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
372 373 374
  }) : super(
    child: child,
    stretchConfiguration: stretchConfiguration,
375
    showOnScreenConfiguration: showOnScreenConfiguration,
376 377
  );
}
Ian Hickson's avatar
Ian Hickson committed
378

379
class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
380
  const _SliverFloatingPersistentHeader({
381 382
    Key? key,
    required SliverPersistentHeaderDelegate delegate,
383 384 385 386
  }) : super(
    key: key,
    delegate: delegate,
  );
Ian Hickson's avatar
Ian Hickson committed
387 388

  @override
389
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
390
    return _RenderSliverFloatingPersistentHeaderForWidgets(
391
      vsync: delegate.vsync,
392 393
      snapConfiguration: delegate.snapConfiguration,
      stretchConfiguration: delegate.stretchConfiguration,
394
      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
395
    );
396 397 398 399
  }

  @override
  void updateRenderObject(BuildContext context, _RenderSliverFloatingPersistentHeaderForWidgets renderObject) {
400
    renderObject.vsync = delegate.vsync;
401
    renderObject.snapConfiguration = delegate.snapConfiguration;
402
    renderObject.stretchConfiguration = delegate.stretchConfiguration;
403
    renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration;
Ian Hickson's avatar
Ian Hickson committed
404 405 406
  }
}

407 408
class _RenderSliverFloatingPinnedPersistentHeaderForWidgets extends RenderSliverFloatingPinnedPersistentHeader
  with _RenderSliverPersistentHeaderForWidgetsMixin {
409
  _RenderSliverFloatingPinnedPersistentHeaderForWidgets({
410 411 412 413 414
    RenderBox? child,
    required TickerProvider? vsync,
    FloatingHeaderSnapConfiguration? snapConfiguration,
    OverScrollHeaderStretchConfiguration? stretchConfiguration,
    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
415 416
  }) : super(
    child: child,
417
    vsync: vsync,
418 419
    snapConfiguration: snapConfiguration,
    stretchConfiguration: stretchConfiguration,
420
    showOnScreenConfiguration: showOnScreenConfiguration,
421
  );
422
}
423 424

class _SliverFloatingPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
425
  const _SliverFloatingPinnedPersistentHeader({
426 427
    Key? key,
    required SliverPersistentHeaderDelegate delegate,
428 429 430 431
  }) : super(
    key: key,
    delegate: delegate,
  );
432 433 434

  @override
  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
435
    return _RenderSliverFloatingPinnedPersistentHeaderForWidgets(
436
      vsync: delegate.vsync,
437 438
      snapConfiguration: delegate.snapConfiguration,
      stretchConfiguration: delegate.stretchConfiguration,
439
      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
440
    );
441 442 443 444
  }

  @override
  void updateRenderObject(BuildContext context, _RenderSliverFloatingPinnedPersistentHeaderForWidgets renderObject) {
445
    renderObject.vsync = delegate.vsync;
446
    renderObject.snapConfiguration = delegate.snapConfiguration;
447
    renderObject.stretchConfiguration = delegate.stretchConfiguration;
448
    renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration;
449 450 451
  }
}

452 453
class _RenderSliverFloatingPersistentHeaderForWidgets extends RenderSliverFloatingPersistentHeader
  with _RenderSliverPersistentHeaderForWidgetsMixin {
454
  _RenderSliverFloatingPersistentHeaderForWidgets({
455 456 457 458 459
    RenderBox? child,
    required TickerProvider? vsync,
    FloatingHeaderSnapConfiguration? snapConfiguration,
    OverScrollHeaderStretchConfiguration? stretchConfiguration,
    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
460 461
  }) : super(
    child: child,
462
    vsync: vsync,
463 464
    snapConfiguration: snapConfiguration,
    stretchConfiguration: stretchConfiguration,
465
    showOnScreenConfiguration: showOnScreenConfiguration,
466
  );
467
}