| 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 'package:flutter/material.dart'; |
| 6 | /// |
| 7 | /// @docImport 'nested_scroll_view.dart'; |
| 8 | /// @docImport 'scroll_view.dart'; |
| 9 | library; |
| 10 | |
| 11 | import 'package:flutter/foundation.dart'; |
| 12 | import 'package:flutter/rendering.dart'; |
| 13 | import 'package:flutter/scheduler.dart' show TickerProvider; |
| 14 | |
| 15 | import 'framework.dart'; |
| 16 | import 'scroll_position.dart'; |
| 17 | import 'scrollable.dart'; |
| 18 | |
| 19 | /// Delegate for configuring a [SliverPersistentHeader]. |
| 20 | abstract class SliverPersistentHeaderDelegate { |
| 21 | /// Abstract const constructor. This constructor enables subclasses to provide |
| 22 | /// const constructors so that they can be used in const expressions. |
| 23 | const SliverPersistentHeaderDelegate(); |
| 24 | |
| 25 | /// The widget to place inside the [SliverPersistentHeader]. |
| 26 | /// |
| 27 | /// The `context` is the [BuildContext] of the sliver. |
| 28 | /// |
| 29 | /// The `shrinkOffset` is a distance from [maxExtent] towards [minExtent] |
| 30 | /// representing the current amount by which the sliver has been shrunk. When |
| 31 | /// the `shrinkOffset` is zero, the contents will be rendered with a dimension |
| 32 | /// of [maxExtent] in the main axis. When `shrinkOffset` equals the difference |
| 33 | /// between [maxExtent] and [minExtent] (a positive number), the contents will |
| 34 | /// be rendered with a dimension of [minExtent] in the main axis. The |
| 35 | /// `shrinkOffset` will always be a positive number in that range. |
| 36 | /// |
| 37 | /// The `overlapsContent` argument is true if subsequent slivers (if any) will |
| 38 | /// be rendered beneath this one, and false if the sliver will not have any |
| 39 | /// contents below it. Typically this is used to decide whether to draw a |
| 40 | /// shadow to simulate the sliver being above the contents below it. Typically |
| 41 | /// this is true when `shrinkOffset` is at its greatest value and false |
| 42 | /// otherwise, but that is not guaranteed. See [NestedScrollView] for an |
| 43 | /// example of a case where `overlapsContent`'s value can be unrelated to |
| 44 | /// `shrinkOffset`. |
| 45 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent); |
| 46 | |
| 47 | /// The smallest size to allow the header to reach, when it shrinks at the |
| 48 | /// start of the viewport. |
| 49 | /// |
| 50 | /// This must return a value equal to or less than [maxExtent]. |
| 51 | /// |
| 52 | /// This value should not change over the lifetime of the delegate. It should |
| 53 | /// be based entirely on the constructor arguments passed to the delegate. See |
| 54 | /// [shouldRebuild], which must return true if a new delegate would return a |
| 55 | /// different value. |
| 56 | double get minExtent; |
| 57 | |
| 58 | /// The size of the header when it is not shrinking at the top of the |
| 59 | /// viewport. |
| 60 | /// |
| 61 | /// This must return a value equal to or greater than [minExtent]. |
| 62 | /// |
| 63 | /// This value should not change over the lifetime of the delegate. It should |
| 64 | /// be based entirely on the constructor arguments passed to the delegate. See |
| 65 | /// [shouldRebuild], which must return true if a new delegate would return a |
| 66 | /// different value. |
| 67 | double get maxExtent; |
| 68 | |
| 69 | /// A [TickerProvider] to use when animating the header's size changes. |
| 70 | /// |
| 71 | /// Must not be null if the persistent header is a floating header, and |
| 72 | /// [snapConfiguration] or [showOnScreenConfiguration] is not null. |
| 73 | TickerProvider? get vsync => null; |
| 74 | |
| 75 | /// Specifies how floating headers should animate in and out of view. |
| 76 | /// |
| 77 | /// If the value of this property is null, then floating headers will |
| 78 | /// not animate into place. |
| 79 | /// |
| 80 | /// This is only used for floating headers (those with |
| 81 | /// [SliverPersistentHeader.floating] set to true). |
| 82 | /// |
| 83 | /// Defaults to null. |
| 84 | FloatingHeaderSnapConfiguration? get snapConfiguration => null; |
| 85 | |
| 86 | /// Specifies an [AsyncCallback] and offset for execution. |
| 87 | /// |
| 88 | /// If the value of this property is null, then callback will not be |
| 89 | /// triggered. |
| 90 | /// |
| 91 | /// This is only used for stretching headers (those with |
| 92 | /// [SliverAppBar.stretch] set to true). |
| 93 | /// |
| 94 | /// Defaults to null. |
| 95 | OverScrollHeaderStretchConfiguration? get stretchConfiguration => null; |
| 96 | |
| 97 | /// Specifies how floating headers and pinned headers should behave in |
| 98 | /// response to [RenderObject.showOnScreen] calls. |
| 99 | /// |
| 100 | /// Defaults to null. |
| 101 | PersistentHeaderShowOnScreenConfiguration? get showOnScreenConfiguration => null; |
| 102 | |
| 103 | /// Whether this delegate is meaningfully different from the old delegate. |
| 104 | /// |
| 105 | /// If this returns false, then the header might not be rebuilt, even though |
| 106 | /// the instance of the delegate changed. |
| 107 | /// |
| 108 | /// This must return true if `oldDelegate` and this object would return |
| 109 | /// different values for [minExtent], [maxExtent], [snapConfiguration], or |
| 110 | /// would return a meaningfully different widget tree from [build] for the |
| 111 | /// same arguments. |
| 112 | bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate); |
| 113 | } |
| 114 | |
| 115 | /// A sliver whose size varies when the sliver is scrolled to the edge |
| 116 | /// of the viewport opposite the sliver's [GrowthDirection]. |
| 117 | /// |
| 118 | /// In the normal case of a [CustomScrollView] with no centered sliver, this |
| 119 | /// sliver will vary its size when scrolled to the leading edge of the viewport. |
| 120 | /// |
| 121 | /// This is the layout primitive that [SliverAppBar] uses for its |
| 122 | /// shrinking/growing effect. |
| 123 | /// |
| 124 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 125 | class SliverPersistentHeader extends StatelessWidget { |
| 126 | /// Creates a sliver that varies its size when it is scrolled to the start of |
| 127 | /// a viewport. |
| 128 | const SliverPersistentHeader({ |
| 129 | super.key, |
| 130 | required this.delegate, |
| 131 | this.pinned = false, |
| 132 | this.floating = false, |
| 133 | }); |
| 134 | |
| 135 | /// Configuration for the sliver's layout. |
| 136 | /// |
| 137 | /// The delegate provides the following information: |
| 138 | /// |
| 139 | /// * The minimum and maximum dimensions of the sliver. |
| 140 | /// |
| 141 | /// * The builder for generating the widgets of the sliver. |
| 142 | /// |
| 143 | /// * The instructions for snapping the scroll offset, if [floating] is true. |
| 144 | final SliverPersistentHeaderDelegate delegate; |
| 145 | |
| 146 | /// Whether to stick the header to the start of the viewport once it has |
| 147 | /// reached its minimum size. |
| 148 | /// |
| 149 | /// If this is false, the header will continue scrolling off the screen after |
| 150 | /// it has shrunk to its minimum extent. |
| 151 | final bool pinned; |
| 152 | |
| 153 | /// Whether the header should immediately grow again if the user reverses |
| 154 | /// scroll direction. |
| 155 | /// |
| 156 | /// If this is false, the header only grows again once the user reaches the |
| 157 | /// part of the viewport that contains the sliver. |
| 158 | /// |
| 159 | /// The [delegate]'s [SliverPersistentHeaderDelegate.snapConfiguration] is |
| 160 | /// ignored unless [floating] is true. |
| 161 | final bool floating; |
| 162 | |
| 163 | @override |
| 164 | Widget build(BuildContext context) { |
| 165 | if (floating && pinned) { |
| 166 | return _SliverFloatingPinnedPersistentHeader(delegate: delegate); |
| 167 | } |
| 168 | if (pinned) { |
| 169 | return _SliverPinnedPersistentHeader(delegate: delegate); |
| 170 | } |
| 171 | if (floating) { |
| 172 | return _SliverFloatingPersistentHeader(delegate: delegate); |
| 173 | } |
| 174 | return _SliverScrollingPersistentHeader(delegate: delegate); |
| 175 | } |
| 176 | |
| 177 | @override |
| 178 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 179 | super.debugFillProperties(properties); |
| 180 | properties.add(DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate' , delegate)); |
| 181 | final List<String> flags = <String>[if (pinned) 'pinned' , if (floating) 'floating' ]; |
| 182 | if (flags.isEmpty) { |
| 183 | flags.add('normal' ); |
| 184 | } |
| 185 | properties.add(IterableProperty<String>('mode' , flags)); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | class _FloatingHeader extends StatefulWidget { |
| 190 | const _FloatingHeader({required this.child}); |
| 191 | |
| 192 | final Widget child; |
| 193 | |
| 194 | @override |
| 195 | _FloatingHeaderState createState() => _FloatingHeaderState(); |
| 196 | } |
| 197 | |
| 198 | // A wrapper for the widget created by _SliverPersistentHeaderElement that |
| 199 | // starts and stops the floating app bar's snap-into-view or snap-out-of-view |
| 200 | // animation. It also informs the float when pointer scrolling by updating the |
| 201 | // last known ScrollDirection when scrolling began. |
| 202 | class _FloatingHeaderState extends State<_FloatingHeader> { |
| 203 | ScrollPosition? _position; |
| 204 | |
| 205 | @override |
| 206 | void didChangeDependencies() { |
| 207 | super.didChangeDependencies(); |
| 208 | if (_position != null) { |
| 209 | _position!.isScrollingNotifier.removeListener(_isScrollingListener); |
| 210 | } |
| 211 | _position = Scrollable.maybeOf(context)?.position; |
| 212 | if (_position != null) { |
| 213 | _position!.isScrollingNotifier.addListener(_isScrollingListener); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | @override |
| 218 | void dispose() { |
| 219 | if (_position != null) { |
| 220 | _position!.isScrollingNotifier.removeListener(_isScrollingListener); |
| 221 | } |
| 222 | super.dispose(); |
| 223 | } |
| 224 | |
| 225 | RenderSliverFloatingPersistentHeader? _headerRenderer() { |
| 226 | return context.findAncestorRenderObjectOfType<RenderSliverFloatingPersistentHeader>(); |
| 227 | } |
| 228 | |
| 229 | void _isScrollingListener() { |
| 230 | assert(_position != null); |
| 231 | |
| 232 | // When a scroll stops, then maybe snap the app bar into view. |
| 233 | // Similarly, when a scroll starts, then maybe stop the snap animation. |
| 234 | // Update the scrolling direction as well for pointer scrolling updates. |
| 235 | final RenderSliverFloatingPersistentHeader? header = _headerRenderer(); |
| 236 | if (_position!.isScrollingNotifier.value) { |
| 237 | header?.updateScrollStartDirection(_position!.userScrollDirection); |
| 238 | // Only SliverAppBars support snapping, headers will not snap. |
| 239 | header?.maybeStopSnapAnimation(_position!.userScrollDirection); |
| 240 | } else { |
| 241 | // Only SliverAppBars support snapping, headers will not snap. |
| 242 | header?.maybeStartSnapAnimation(_position!.userScrollDirection); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | @override |
| 247 | Widget build(BuildContext context) => widget.child; |
| 248 | } |
| 249 | |
| 250 | class _SliverPersistentHeaderElement extends RenderObjectElement { |
| 251 | _SliverPersistentHeaderElement( |
| 252 | _SliverPersistentHeaderRenderObjectWidget super.widget, { |
| 253 | this.floating = false, |
| 254 | }); |
| 255 | |
| 256 | final bool floating; |
| 257 | |
| 258 | @override |
| 259 | _RenderSliverPersistentHeaderForWidgetsMixin get renderObject => |
| 260 | super.renderObject as _RenderSliverPersistentHeaderForWidgetsMixin; |
| 261 | |
| 262 | @override |
| 263 | void mount(Element? parent, Object? newSlot) { |
| 264 | super.mount(parent, newSlot); |
| 265 | renderObject._element = this; |
| 266 | } |
| 267 | |
| 268 | @override |
| 269 | void unmount() { |
| 270 | renderObject._element = null; |
| 271 | super.unmount(); |
| 272 | } |
| 273 | |
| 274 | @override |
| 275 | void update(_SliverPersistentHeaderRenderObjectWidget newWidget) { |
| 276 | final _SliverPersistentHeaderRenderObjectWidget oldWidget = |
| 277 | widget as _SliverPersistentHeaderRenderObjectWidget; |
| 278 | super.update(newWidget); |
| 279 | final SliverPersistentHeaderDelegate newDelegate = newWidget.delegate; |
| 280 | final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate; |
| 281 | if (newDelegate != oldDelegate && |
| 282 | (newDelegate.runtimeType != oldDelegate.runtimeType || |
| 283 | newDelegate.shouldRebuild(oldDelegate))) { |
| 284 | final _RenderSliverPersistentHeaderForWidgetsMixin renderObject = this.renderObject; |
| 285 | _updateChild(newDelegate, renderObject.lastShrinkOffset, renderObject.lastOverlapsContent); |
| 286 | renderObject.triggerRebuild(); |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | @override |
| 291 | void performRebuild() { |
| 292 | super.performRebuild(); |
| 293 | renderObject.triggerRebuild(); |
| 294 | } |
| 295 | |
| 296 | Element? child; |
| 297 | |
| 298 | void _updateChild( |
| 299 | SliverPersistentHeaderDelegate delegate, |
| 300 | double shrinkOffset, |
| 301 | bool overlapsContent, |
| 302 | ) { |
| 303 | final Widget newWidget = delegate.build(this, shrinkOffset, overlapsContent); |
| 304 | child = updateChild(child, floating ? _FloatingHeader(child: newWidget) : newWidget, null); |
| 305 | } |
| 306 | |
| 307 | void _build(double shrinkOffset, bool overlapsContent) { |
| 308 | owner!.buildScope(this, () { |
| 309 | final _SliverPersistentHeaderRenderObjectWidget sliverPersistentHeaderRenderObjectWidget = |
| 310 | widget as _SliverPersistentHeaderRenderObjectWidget; |
| 311 | _updateChild( |
| 312 | sliverPersistentHeaderRenderObjectWidget.delegate, |
| 313 | shrinkOffset, |
| 314 | overlapsContent, |
| 315 | ); |
| 316 | }); |
| 317 | } |
| 318 | |
| 319 | @override |
| 320 | void forgetChild(Element child) { |
| 321 | assert(child == this.child); |
| 322 | this.child = null; |
| 323 | super.forgetChild(child); |
| 324 | } |
| 325 | |
| 326 | @override |
| 327 | void insertRenderObjectChild(covariant RenderBox child, Object? slot) { |
| 328 | assert(renderObject.debugValidateChild(child)); |
| 329 | renderObject.child = child; |
| 330 | } |
| 331 | |
| 332 | @override |
| 333 | void moveRenderObjectChild(covariant RenderObject child, Object? oldSlot, Object? newSlot) { |
| 334 | assert(false); |
| 335 | } |
| 336 | |
| 337 | @override |
| 338 | void removeRenderObjectChild(covariant RenderObject child, Object? slot) { |
| 339 | renderObject.child = null; |
| 340 | } |
| 341 | |
| 342 | @override |
| 343 | void visitChildren(ElementVisitor visitor) { |
| 344 | if (child != null) { |
| 345 | visitor(child!); |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWidget { |
| 351 | const _SliverPersistentHeaderRenderObjectWidget({required this.delegate, this.floating = false}); |
| 352 | |
| 353 | final SliverPersistentHeaderDelegate delegate; |
| 354 | final bool floating; |
| 355 | |
| 356 | @override |
| 357 | _SliverPersistentHeaderElement createElement() => |
| 358 | _SliverPersistentHeaderElement(this, floating: floating); |
| 359 | |
| 360 | @override |
| 361 | _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context); |
| 362 | |
| 363 | @override |
| 364 | void debugFillProperties(DiagnosticPropertiesBuilder description) { |
| 365 | super.debugFillProperties(description); |
| 366 | description.add(DiagnosticsProperty<SliverPersistentHeaderDelegate>('delegate' , delegate)); |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | mixin _RenderSliverPersistentHeaderForWidgetsMixin on RenderSliverPersistentHeader { |
| 371 | _SliverPersistentHeaderElement? _element; |
| 372 | |
| 373 | @override |
| 374 | double get minExtent => |
| 375 | (_element!.widget as _SliverPersistentHeaderRenderObjectWidget).delegate.minExtent; |
| 376 | |
| 377 | @override |
| 378 | double get maxExtent => |
| 379 | (_element!.widget as _SliverPersistentHeaderRenderObjectWidget).delegate.maxExtent; |
| 380 | |
| 381 | @override |
| 382 | void updateChild(double shrinkOffset, bool overlapsContent) { |
| 383 | assert(_element != null); |
| 384 | _element!._build(shrinkOffset, overlapsContent); |
| 385 | } |
| 386 | |
| 387 | @protected |
| 388 | void triggerRebuild() { |
| 389 | markNeedsLayout(); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | class _SliverScrollingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget { |
| 394 | const _SliverScrollingPersistentHeader({required super.delegate}); |
| 395 | |
| 396 | @override |
| 397 | _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) { |
| 398 | return _RenderSliverScrollingPersistentHeaderForWidgets( |
| 399 | stretchConfiguration: delegate.stretchConfiguration, |
| 400 | ); |
| 401 | } |
| 402 | |
| 403 | @override |
| 404 | void updateRenderObject( |
| 405 | BuildContext context, |
| 406 | covariant _RenderSliverScrollingPersistentHeaderForWidgets renderObject, |
| 407 | ) { |
| 408 | renderObject.stretchConfiguration = delegate.stretchConfiguration; |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | class _RenderSliverScrollingPersistentHeaderForWidgets extends RenderSliverScrollingPersistentHeader |
| 413 | with _RenderSliverPersistentHeaderForWidgetsMixin { |
| 414 | _RenderSliverScrollingPersistentHeaderForWidgets({super.stretchConfiguration}); |
| 415 | } |
| 416 | |
| 417 | class _SliverPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget { |
| 418 | const _SliverPinnedPersistentHeader({required super.delegate}); |
| 419 | |
| 420 | @override |
| 421 | _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) { |
| 422 | return _RenderSliverPinnedPersistentHeaderForWidgets( |
| 423 | stretchConfiguration: delegate.stretchConfiguration, |
| 424 | showOnScreenConfiguration: delegate.showOnScreenConfiguration, |
| 425 | ); |
| 426 | } |
| 427 | |
| 428 | @override |
| 429 | void updateRenderObject( |
| 430 | BuildContext context, |
| 431 | covariant _RenderSliverPinnedPersistentHeaderForWidgets renderObject, |
| 432 | ) { |
| 433 | renderObject |
| 434 | ..stretchConfiguration = delegate.stretchConfiguration |
| 435 | ..showOnScreenConfiguration = delegate.showOnScreenConfiguration; |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | class _RenderSliverPinnedPersistentHeaderForWidgets extends RenderSliverPinnedPersistentHeader |
| 440 | with _RenderSliverPersistentHeaderForWidgetsMixin { |
| 441 | _RenderSliverPinnedPersistentHeaderForWidgets({ |
| 442 | super.stretchConfiguration, |
| 443 | super.showOnScreenConfiguration, |
| 444 | }); |
| 445 | } |
| 446 | |
| 447 | class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget { |
| 448 | const _SliverFloatingPersistentHeader({required super.delegate}) : super(floating: true); |
| 449 | |
| 450 | @override |
| 451 | _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) { |
| 452 | return _RenderSliverFloatingPersistentHeaderForWidgets( |
| 453 | vsync: delegate.vsync, |
| 454 | snapConfiguration: delegate.snapConfiguration, |
| 455 | stretchConfiguration: delegate.stretchConfiguration, |
| 456 | showOnScreenConfiguration: delegate.showOnScreenConfiguration, |
| 457 | ); |
| 458 | } |
| 459 | |
| 460 | @override |
| 461 | void updateRenderObject( |
| 462 | BuildContext context, |
| 463 | _RenderSliverFloatingPersistentHeaderForWidgets renderObject, |
| 464 | ) { |
| 465 | renderObject.vsync = delegate.vsync; |
| 466 | renderObject.snapConfiguration = delegate.snapConfiguration; |
| 467 | renderObject.stretchConfiguration = delegate.stretchConfiguration; |
| 468 | renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration; |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | class _RenderSliverFloatingPinnedPersistentHeaderForWidgets |
| 473 | extends RenderSliverFloatingPinnedPersistentHeader |
| 474 | with _RenderSliverPersistentHeaderForWidgetsMixin { |
| 475 | _RenderSliverFloatingPinnedPersistentHeaderForWidgets({ |
| 476 | required super.vsync, |
| 477 | super.snapConfiguration, |
| 478 | super.stretchConfiguration, |
| 479 | super.showOnScreenConfiguration, |
| 480 | }); |
| 481 | } |
| 482 | |
| 483 | class _SliverFloatingPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget { |
| 484 | const _SliverFloatingPinnedPersistentHeader({required super.delegate}) : super(floating: true); |
| 485 | |
| 486 | @override |
| 487 | _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) { |
| 488 | return _RenderSliverFloatingPinnedPersistentHeaderForWidgets( |
| 489 | vsync: delegate.vsync, |
| 490 | snapConfiguration: delegate.snapConfiguration, |
| 491 | stretchConfiguration: delegate.stretchConfiguration, |
| 492 | showOnScreenConfiguration: delegate.showOnScreenConfiguration, |
| 493 | ); |
| 494 | } |
| 495 | |
| 496 | @override |
| 497 | void updateRenderObject( |
| 498 | BuildContext context, |
| 499 | _RenderSliverFloatingPinnedPersistentHeaderForWidgets renderObject, |
| 500 | ) { |
| 501 | renderObject.vsync = delegate.vsync; |
| 502 | renderObject.snapConfiguration = delegate.snapConfiguration; |
| 503 | renderObject.stretchConfiguration = delegate.stretchConfiguration; |
| 504 | renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration; |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | class _RenderSliverFloatingPersistentHeaderForWidgets extends RenderSliverFloatingPersistentHeader |
| 509 | with _RenderSliverPersistentHeaderForWidgetsMixin { |
| 510 | _RenderSliverFloatingPersistentHeaderForWidgets({ |
| 511 | required super.vsync, |
| 512 | super.snapConfiguration, |
| 513 | super.stretchConfiguration, |
| 514 | super.showOnScreenConfiguration, |
| 515 | }); |
| 516 | } |
| 517 | |