scrollable_grid.dart 4.83 KB
Newer Older
1 2 3 4 5 6
// Copyright 2015 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;

7
import 'package:collection/collection.dart' show lowerBound;
8 9
import 'package:flutter/rendering.dart';

10
import 'framework.dart';
11
import 'scroll_behavior.dart';
12
import 'scrollable.dart';
Adam Barth's avatar
Adam Barth committed
13
import 'virtual_viewport.dart';
14 15 16 17 18 19 20 21

/// A vertically scrollable grid.
///
/// Requires that delegate places its children in row-major order.
class ScrollableGrid extends Scrollable {
  ScrollableGrid({
    Key key,
    double initialScrollOffset,
22
    ScrollListener onScrollStart,
23
    ScrollListener onScroll,
24
    ScrollListener onScrollEnd,
25 26 27 28 29 30 31 32 33
    SnapOffsetCallback snapOffsetCallback,
    this.delegate,
    this.children
  }) : super(
    key: key,
    initialScrollOffset: initialScrollOffset,
    // TODO(abarth): Support horizontal offsets. For horizontally scrolling
    // grids. For horizontally scrolling grids, we'll probably need to use a
    // delegate that places children in column-major order.
34
    scrollDirection: Axis.vertical,
35
    onScrollStart: onScrollStart,
36
    onScroll: onScroll,
37
    onScrollEnd: onScrollEnd,
38
    snapOffsetCallback: snapOffsetCallback
39 40 41
  );

  final GridDelegate delegate;
42
  final Iterable<Widget> children;
43

44
  @override
Adam Barth's avatar
Adam Barth committed
45
  ScrollableState createState() => new _ScrollableGridState();
46 47
}

Adam Barth's avatar
Adam Barth committed
48
class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
49
  @override
50
  ExtentScrollBehavior createScrollBehavior() => new OverscrollBehavior();
51 52

  @override
53 54 55 56
  ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;

  void _handleExtentsChanged(double contentExtent, double containerExtent) {
    setState(() {
57
      didUpdateScrollBehavior(scrollBehavior.updateExtents(
58 59 60 61 62 63 64
        contentExtent: contentExtent,
        containerExtent: containerExtent,
        scrollOffset: scrollOffset
      ));
    });
  }

65
  @override
66 67 68 69 70 71 72 73 74 75
  Widget buildContent(BuildContext context) {
    return new GridViewport(
      startOffset: scrollOffset,
      delegate: config.delegate,
      onExtentsChanged: _handleExtentsChanged,
      children: config.children
    );
  }
}

76
class GridViewport extends VirtualViewportFromIterable {
77 78 79 80 81 82 83
  GridViewport({
    this.startOffset,
    this.delegate,
    this.onExtentsChanged,
    this.children
  });

84
  @override
85 86 87
  final double startOffset;
  final GridDelegate delegate;
  final ExtentsChangedCallback onExtentsChanged;
88 89

  @override
90
  final Iterable<Widget> children;
91

92 93
  // TODO(abarth): Support horizontal grids.
  Axis get mainAxis => Axis.vertical;
94

95
  @override
96
  RenderGrid createRenderObject(BuildContext context) => new RenderGrid(delegate: delegate);
97

98
  @override
99 100 101
  _GridViewportElement createElement() => new _GridViewportElement(this);
}

102
class _GridViewportElement extends VirtualViewportElement {
103 104
  _GridViewportElement(GridViewport widget) : super(widget);

105
  @override
106 107
  GridViewport get widget => super.widget;

108
  @override
Adam Barth's avatar
Adam Barth committed
109
  RenderGrid get renderObject => super.renderObject;
110

111
  @override
Adam Barth's avatar
Adam Barth committed
112
  int get materializedChildBase => _materializedChildBase;
113 114
  int _materializedChildBase;

115
  @override
Adam Barth's avatar
Adam Barth committed
116 117
  int get materializedChildCount => _materializedChildCount;
  int _materializedChildCount;
118

119
  @override
120 121
  double get startOffsetBase => _startOffsetBase;
  double _startOffsetBase;
122

123
  @override
124 125
  double get startOffsetLimit =>_startOffsetLimit;
  double _startOffsetLimit;
126

127
  @override
128
  void updateRenderObject(GridViewport oldWidget) {
129
    renderObject.delegate = widget.delegate;
130
    super.updateRenderObject(oldWidget);
131 132
  }

133 134
  double _lastReportedContentExtent;
  double _lastReportedContainerExtent;
Adam Barth's avatar
Adam Barth committed
135
  GridSpecification _specification;
136

137
  @override
138 139 140 141 142
  void layout(BoxConstraints constraints) {
    _specification = renderObject.specification;
    double contentExtent = _specification.gridSize.height;
    double containerExtent = renderObject.size.height;

143 144
    int materializedRowBase = math.max(0, lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
    int materializedRowLimit = math.min(_specification.rowCount, lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
145

146 147
    _materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
    _materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
148 149
    _startOffsetBase = _specification.rowOffsets[materializedRowBase];
    _startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
150

Adam Barth's avatar
Adam Barth committed
151
    super.layout(constraints);
152

153 154 155 156
    if (contentExtent != _lastReportedContentExtent || containerExtent != _lastReportedContainerExtent) {
      _lastReportedContentExtent = contentExtent;
      _lastReportedContainerExtent = containerExtent;
      widget.onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent);
157 158 159
    }
  }
}