viewport_offset.dart 7.02 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright 2016 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:meta/meta.dart';

/// The direction of a scroll, relative to the positive scroll offset axis given
/// by an [AxisDirection] and a [GrowthDirection].
///
/// This contrasts to [GrowthDirection] in that it has a third value, [idle],
/// for the case where no scroll is occurring.
///
14 15 16
/// This is used by [RenderSliverFloatingPersistentHeader] to only expand when
/// the user is scrolling in the same direction as the detected scroll offset
/// change.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
enum ScrollDirection {
  /// No scrolling is underway.
  idle,

  /// Scrolling is happening in the positive scroll offset direction.
  ///
  /// For example, for the [GrowthDirection.forward] part of a vertical
  /// [AxisDirection.down] list, this means the content is moving up, exposing
  /// lower content.
  forward,

  /// Scrolling is happening in the negative scroll offset direction.
  ///
  /// For example, for the [GrowthDirection.forward] part of a vertical
  /// [AxisDirection.down] list, this means the content is moving down, exposing
  /// earlier content.
  reverse,
}

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/// Returns the opposite of the given [ScrollDirection].
///
/// Specifically, returns [AxisDirection.reverse] for [AxisDirection.forward]
/// (and vice versa) and returns [ScrollDirection.idle] for
/// [ScrollDirection.idle].
ScrollDirection flipScrollDirection(ScrollDirection direction) {
  switch (direction) {
    case ScrollDirection.idle:
      return ScrollDirection.idle;
    case ScrollDirection.forward:
      return ScrollDirection.reverse;
    case ScrollDirection.reverse:
      return ScrollDirection.forward;
  }
  return null;
}

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
abstract class ViewportOffset extends ChangeNotifier {
  ViewportOffset();
  factory ViewportOffset.fixed(double value) = _FixedViewportOffset;
  factory ViewportOffset.zero() = _FixedViewportOffset.zero;

  /// The number of pixels to offset the children in the opposite of the axis direction.
  ///
  /// For example, if the axis direction is down, then the pixel value
  /// represents the number of logical pixels to move the children _up_ the
  /// screen. Similarly, if the axis direction is left, then the pixels value
  /// represents the number of logical pixesl to move the children to _right_.
  double get pixels;

  /// Called when the viewport's extents are established.
  ///
Adam Barth's avatar
Adam Barth committed
68
  /// The argument is the dimension of the [RenderViewport] in the main axis
69 70 71
  /// (e.g. the height, for a vertical viewport).
  ///
  /// This may be called redundantly, with the same value, each frame. This is
Adam Barth's avatar
Adam Barth committed
72
  /// called during layout for the [RenderViewport]. If the viewport is
73 74 75 76 77 78 79 80
  /// configured to shrink-wrap its contents, it may be called several times,
  /// since the layout is repeated each time the scroll offset is corrected.
  ///
  /// If this is called, it is called before [applyContentDimensions]. If this
  /// is called, [applyContentDimensions] will be called soon afterwards in the
  /// same layout phase. If the viewport is not configured to shrink-wrap its
  /// contents, then this will only be called when the viewport recomputes its
  /// size (i.e. when its parent lays out), and not during normal scrolling.
81 82
  ///
  /// If applying the viewport dimentions changes the scroll offset, return
Adam Barth's avatar
Adam Barth committed
83
  /// false. Otherwise, return true. If you return false, the [RenderViewport]
84 85 86 87 88 89
  /// will be laid out again with the new scroll offset. This is expensive. (The
  /// return value is answering the question "did you accept these viewport
  /// dimensions unconditionally?"; if the new dimensions change the
  /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to
  /// be laid out again.)
  bool applyViewportDimension(double viewportDimension);
90 91 92 93 94 95 96 97 98 99 100 101 102 103

  /// Called when the viewport's content extents are established.
  ///
  /// The arguments are the minimum and maximum scroll extents respectively. The
  /// minimum will be equal to or less than zero, the maximum will be equal to
  /// or greater than zero.
  ///
  /// The maximum scroll extent has the viewport dimension subtracted from it.
  /// For instance, if there is 100.0 pixels of scrollable content, and the
  /// viewport is 80.0 pixels high, then the minimum scroll extent will
  /// typically be 0.0 and the maximum scroll extent will typically be 20.0,
  /// because there's only 20.0 pixels of actual scroll slack.
  ///
  /// If applying the content dimensions changes the scroll offset, return
Adam Barth's avatar
Adam Barth committed
104
  /// false. Otherwise, return true. If you return false, the [RenderViewport]
105 106 107 108 109 110
  /// will be laid out again with the new scroll offset. This is expensive. (The
  /// return value is answering the question "did you accept these content
  /// dimensions unconditionally?"; if the new dimensions change the
  /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to
  /// be laid out again.)
  ///
Adam Barth's avatar
Adam Barth committed
111
  /// This is called at least once each time the [RenderViewport] is laid out,
112 113 114 115 116 117 118 119 120
  /// even if the values have not changed. It may be called many times if the
  /// scroll offset is corrected (if this returns false). This is always called
  /// after [applyViewportDimension], if that method is called.
  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent);

  /// Apply a layout-time correction to the scroll offset.
  ///
  /// This method should change the [pixels] value by `correction`, but without
  /// calling [notifyListeners]. It is called during layout by the
Adam Barth's avatar
Adam Barth committed
121
  /// [RenderViewport], before [applyContentDimensions]. After this method is
122 123 124 125 126
  /// called, the layout will be recomputed and that may result in this method
  /// being called again, though this should be very rare.
  void correctBy(double correction);

  /// The direction in which the user is trying to change [pixels], relative to
Adam Barth's avatar
Adam Barth committed
127
  /// the viewport's [RenderViewport.axisDirection].
128 129
  ///
  /// This is used by some slivers to determine how to react to a change in
130 131 132
  /// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will
  /// only expand a floating app bar when the [userScrollDirection] is in the
  /// positive scroll offset direction.
133 134 135 136 137 138 139 140 141 142 143
  ScrollDirection get userScrollDirection;

  @override
  String toString() {
    final List<String> description = <String>[];
    debugFillDescription(description);
    return '$runtimeType(${description.join(", ")})';
  }

  @mustCallSuper
  void debugFillDescription(List<String> description) {
144
    description.add('offset: ${pixels?.toStringAsFixed(1)}');
145 146 147 148 149 150 151 152 153 154 155 156 157
  }
}

class _FixedViewportOffset extends ViewportOffset {
  _FixedViewportOffset(this._pixels);
  _FixedViewportOffset.zero() : _pixels = 0.0;

  double _pixels;

  @override
  double get pixels => _pixels;

  @override
158
  bool applyViewportDimension(double viewportDimension) => true;
159 160 161 162 163 164 165 166 167 168 169 170

  @override
  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true;

  @override
  void correctBy(double correction) {
    _pixels += correction;
  }

  @override
  ScrollDirection get userScrollDirection => ScrollDirection.idle;
}