sliver_fill.dart 7.42 KB
Newer Older
1 2 3 4 5 6 7 8 9
// Copyright 2017 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 'dart:math' as math;

import 'package:flutter/foundation.dart';

import 'box.dart';
10
import 'object.dart';
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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
29
///    extent in the main axis.
30 31 32 33 34 35 36
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({
    @required RenderSliverBoxChildManager childManager,
37
    double viewportFraction = 1.0,
38 39 40 41
  }) : assert(viewportFraction != null),
       assert(viewportFraction > 0.0),
       _viewportFraction = viewportFraction,
       super(childManager: childManager);
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

  @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();
  }

  double get _padding => (1.0 - viewportFraction) * constraints.viewportMainAxisExtent * 0.5;

  @override
  double indexToLayoutOffset(double itemExtent, int index) {
    return _padding + super.indexToLayoutOffset(itemExtent, index);
  }

  @override
  int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
    return super.getMinChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
  }

  @override
  int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
    return super.getMaxChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
  }

  @override
79 80
  double estimateMaxScrollOffset(
    SliverConstraints constraints, {
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    int firstIndex,
    int lastIndex,
    double leadingScrollOffset,
    double trailingScrollOffset,
  }) {
    final double padding = _padding;
    return childManager.estimateMaxScrollOffset(
      constraints,
      firstIndex: firstIndex,
      lastIndex: lastIndex,
      leadingScrollOffset: leadingScrollOffset - padding,
      trailingScrollOffset: trailingScrollOffset - padding,
    ) + padding + padding;
  }
}

/// A sliver that contains a single box child that 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:
///
108 109
///  * [RenderSliverFillViewport], which sizes its children based on the
///    size of the viewport, regardless of what else is in the scroll view.
110 111 112 113 114 115 116
///  * [RenderSliverList], which shows a list of variable-sized children in a
///    viewport.
class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
  /// Creates a [RenderSliver] that wraps a [RenderBox] which is sized to fit
  /// the remaining space in the viewport.
  RenderSliverFillRemaining({
    RenderBox child,
117
    this.hasScrollBody = true,
118
    this.fillOverscroll = false,
119 120 121
  }) : assert(hasScrollBody != null),
       super(child: child);

122 123
  /// Indicates whether the child has a scrollable body, this value cannot be
  /// null.
124 125 126 127 128
  ///
  /// Defaults to true such that the child will extend beyond the viewport and
  /// scroll, as seen in [NestedScrollView].
  ///
  /// Setting this value to false will allow the child to fill the remainder of
129 130 131
  /// the viewport and not extend further. However, if the
  /// [precedingScrollExtent] exceeds the size of the viewport, the sliver will
  /// defer to the child's size rather than overriding it.
132
  bool hasScrollBody;
133

134 135 136 137 138 139 140 141 142
  /// Indicates whether the child should stretch to fill the overscroll area
  /// created by certain scroll physics, such as iOS' default scroll physics.
  /// This value cannot be null. This flag is only relevant when the
  /// [hasScrollBody] value is false.
  ///
  /// Defaults to false, meaning the default behavior is for the child to
  /// maintain its size and not extend into the overscroll area.
  bool fillOverscroll;

143 144
  @override
  void performLayout() {
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    double childExtent;
    double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
    double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);

    if (hasScrollBody) {
      extent = maxExtent;
      if (child != null)
        child.layout(
          constraints.asBoxConstraints(
            minExtent: extent,
            maxExtent: extent,
          ),
          parentUsesSize: true,
        );
    } else if (child != null) {
      child.layout(constraints.asBoxConstraints(), parentUsesSize: true);

      switch (constraints.axis) {
        case Axis.horizontal:
          childExtent = child.size.width;
          break;
        case Axis.vertical:
          childExtent = child.size.height;
          break;
      }
      if (constraints.precedingScrollExtent > constraints.viewportMainAxisExtent || childExtent > extent)
        extent = childExtent;
      if (maxExtent < extent)
        maxExtent = extent;
      if ((fillOverscroll ? maxExtent : extent) > childExtent) {
        child.layout(
          constraints.asBoxConstraints(
            minExtent: extent,
            maxExtent: fillOverscroll ? maxExtent : extent,
          ),
          parentUsesSize: true,
        );
      }
    }

    assert(extent.isFinite,
      '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'
        'false.',
    );
191
    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
192 193
    assert(paintedChildSize.isFinite);
    assert(paintedChildSize >= 0.0);
194
    geometry = SliverGeometry(
195
      scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : extent,
196 197
      paintExtent: paintedChildSize,
      maxPaintExtent: paintedChildSize,
198
      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
199 200 201 202 203
    );
    if (child != null)
      setChildParentData(child, constraints, geometry);
  }
}