sliver_fill.dart 10.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math' as math;

import 'box.dart';
8
import 'object.dart';
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import 'sliver.dart';
import 'sliver_fixed_extent_list.dart';
import 'sliver_multi_box_adaptor.dart';

/// A sliver that contains multiple box children that each fill the viewport.
///
/// [RenderSliverFillViewport] places its children in a linear array along the
/// main axis. Each child is sized to fill the viewport, both in the main and
/// cross axis. A [viewportFraction] factor can be provided to size the children
/// to a multiple of the viewport's main axis dimension (typically a fraction
/// less than 1.0).
///
/// See also:
///
///  * [RenderSliverFillRemaining], which sizes the children based on the
///    remaining space rather than the viewport itself.
///  * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
///  * [RenderSliverList], which does not require its children to have the same
27
///    extent in the main axis.
28 29 30 31 32 33
class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
  /// Creates a sliver that contains multiple box children that each fill the
  /// viewport.
  ///
  /// The [childManager] argument must not be null.
  RenderSliverFillViewport({
34
    required RenderSliverBoxChildManager childManager,
35
    double viewportFraction = 1.0,
36 37 38 39
  }) : assert(viewportFraction != null),
       assert(viewportFraction > 0.0),
       _viewportFraction = viewportFraction,
       super(childManager: childManager);
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

  @override
  double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction;

  /// The fraction of the viewport that each child should fill in the main axis.
  ///
  /// If this fraction is less than 1.0, more than one child will be visible at
  /// once. If this fraction is greater than 1.0, each child will be larger than
  /// the viewport in the main axis.
  double get viewportFraction => _viewportFraction;
  double _viewportFraction;
  set viewportFraction(double value) {
    assert(value != null);
    if (_viewportFraction == value)
      return;
    _viewportFraction = value;
    markNeedsLayout();
  }
}

60 61
/// A sliver that contains a single box child that contains a scrollable and
/// fills the viewport.
62
///
63 64 65
/// [RenderSliverFillRemainingWithScrollable] sizes its child to fill the
/// viewport in the cross axis and to fill the remaining space in the viewport
/// in the main axis.
66 67 68 69 70 71
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
72 73 74 75 76 77 78 79 80 81 82 83
///  * [NestedScrollView], which uses this sliver for the inner scrollable.
///  * [RenderSliverFillRemaining], which lays out its
///    non-scrollable child slightly different than this widget.
///  * [RenderSliverFillRemainingAndOverscroll], which incorporates the
///    overscroll into the remaining space to fill.
///  * [RenderSliverFillViewport], which sizes its children based on the
///    size of the viewport, regardless of what else is in the scroll view.
///  * [RenderSliverList], which shows a list of variable-sized children in a
///    viewport.
class RenderSliverFillRemainingWithScrollable extends RenderSliverSingleBoxAdapter {
  /// Creates a [RenderSliver] that wraps a scrollable [RenderBox] which is
  /// sized to fit the remaining space in the viewport.
84
  RenderSliverFillRemainingWithScrollable({ RenderBox? child }) : super(child: child);
85 86 87

  @override
  void performLayout() {
88
    final SliverConstraints constraints = this.constraints;
89 90 91
    final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);

    if (child != null)
92
      child!.layout(constraints.asBoxConstraints(
93 94 95 96 97 98 99 100 101 102 103 104 105 106
        minExtent: extent,
        maxExtent: extent,
      ));

    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);
    geometry = SliverGeometry(
      scrollExtent: constraints.viewportMainAxisExtent,
      paintExtent: paintedChildSize,
      maxPaintExtent: paintedChildSize,
      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
    );
    if (child != null)
107
      setChildParentData(child!, constraints, geometry!);
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
  }
}

/// A sliver that contains a single box child that is non-scrollable and fills
/// the remaining space in the viewport.
///
/// [RenderSliverFillRemaining] sizes its child to fill the
/// viewport in the cross axis and to fill the remaining space in the viewport
/// in the main axis.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
///  * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
///    child slightly different than this widget.
///  * [RenderSliverFillRemainingAndOverscroll], which incorporates the
///    overscroll into the remaining space to fill.
127 128
///  * [RenderSliverFillViewport], which sizes its children based on the
///    size of the viewport, regardless of what else is in the scroll view.
129 130 131
///  * [RenderSliverList], which shows a list of variable-sized children in a
///    viewport.
class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
132 133
  /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
  /// sized to fit the remaining space in the viewport.
134
  RenderSliverFillRemaining({ RenderBox? child }) : super(child: child);
135

136 137
  @override
  void performLayout() {
138
    final SliverConstraints constraints = this.constraints;
139 140
    // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
    // scrolled beyond the extent of the screen.
141 142
    double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;

143
    if (child != null) {
144
      final double childExtent;
145 146
      switch (constraints.axis) {
        case Axis.horizontal:
147
          childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
148 149
          break;
        case Axis.vertical:
150
          childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
151 152
          break;
      }
153

154 155 156 157
      // If the childExtent is greater than the computed extent, we want to use
      // that instead of potentially cutting off the child. This allows us to
      // safely specify a maxExtent.
      extent = math.max(extent, childExtent);
158
      child!.layout(constraints.asBoxConstraints(
159 160 161
        minExtent: extent,
        maxExtent: extent,
      ));
162 163 164
    }

    assert(extent.isFinite,
165 166 167
      'The calculated extent for the child of SliverFillRemaining is not finite. '
      'This can happen if the child is a scrollable, in which case, the '
      'hasScrollBody property of SliverFillRemaining should not be set to '
168
      'false.',
169
    );
170
    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
171 172
    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);
173
    geometry = SliverGeometry(
174
      scrollExtent: extent,
175 176
      paintExtent: paintedChildSize,
      maxPaintExtent: paintedChildSize,
177
      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
178 179
    );
    if (child != null)
180
      setChildParentData(child!, constraints, geometry!);
181 182
  }
}
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

/// A sliver that contains a single box child that is non-scrollable and fills
/// the remaining space in the viewport including any overscrolled area.
///
/// [RenderSliverFillRemainingAndOverscroll] sizes its child to fill the
/// viewport in the cross axis and to fill the remaining space in the viewport
/// in the main axis with the overscroll area included.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
///  * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
///    child without overscroll.
///  * [RenderSliverFillRemaining], which lays out its
///    non-scrollable child without overscroll.
///  * [RenderSliverFillViewport], which sizes its children based on the
///    size of the viewport, regardless of what else is in the scroll view.
///  * [RenderSliverList], which shows a list of variable-sized children in a
///    viewport.
class RenderSliverFillRemainingAndOverscroll extends RenderSliverSingleBoxAdapter {
  /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
  /// sized to fit the remaining space plus any overscroll in the viewport.
207
  RenderSliverFillRemainingAndOverscroll({ RenderBox? child }) : super(child: child);
208 209 210

  @override
  void performLayout() {
211
    final SliverConstraints constraints = this.constraints;
212 213 214 215 216 217 218 219
    // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
    // scrolled beyond the extent of the screen.
    double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
    // The maxExtent includes any overscrolled area. Can be < 0 if we have
    // overscroll in the opposite direction, away from the end of the list.
    double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);

    if (child != null) {
220
      final double childExtent;
221 222
      switch (constraints.axis) {
        case Axis.horizontal:
223
          childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
224 225
          break;
        case Axis.vertical:
226
          childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
227 228 229 230 231 232 233 234 235 236 237
          break;
      }

      // If the childExtent is greater than the computed extent, we want to use
      // that instead of potentially cutting off the child. This allows us to
      // safely specify a maxExtent.
      extent = math.max(extent, childExtent);
      // The extent could be larger than the maxExtent due to a larger child
      // size or overscrolling at the top of the scrollable (rather than at the
      // end where this sliver is).
      maxExtent = math.max(extent, maxExtent);
238
      child!.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: maxExtent));
239 240 241
    }

    assert(extent.isFinite,
242 243 244
      'The calculated extent for the child of SliverFillRemaining is not finite. '
      'This can happen if the child is a scrollable, in which case, the '
      'hasScrollBody property of SliverFillRemaining should not be set to '
245 246 247 248 249 250 251 252 253 254 255 256
      'false.',
    );
    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);
    geometry = SliverGeometry(
      scrollExtent: extent,
      paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
      maxPaintExtent: maxExtent,
      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
    );
    if (child != null)
257
      setChildParentData(child!, constraints, geometry!);
258 259
  }
}