scrollable_grid.dart 4.79 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 8
import 'package:flutter/rendering.dart';

9
import 'framework.dart';
10
import 'scroll_behavior.dart';
11
import 'scrollable.dart';
Adam Barth's avatar
Adam Barth committed
12
import 'virtual_viewport.dart';
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

/// A vertically scrollable grid.
///
/// Requires that delegate places its children in row-major order.
class ScrollableGrid extends Scrollable {
  ScrollableGrid({
    Key key,
    double initialScrollOffset,
    ScrollListener onScroll,
    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.
31
    scrollDirection: Axis.vertical,
32
    onScroll: onScroll,
33
    snapOffsetCallback: snapOffsetCallback
34 35 36
  );

  final GridDelegate delegate;
37
  final Iterable<Widget> children;
38

Adam Barth's avatar
Adam Barth committed
39
  ScrollableState createState() => new _ScrollableGridState();
40 41
}

Adam Barth's avatar
Adam Barth committed
42 43
class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
  ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
  ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;

  void _handleExtentsChanged(double contentExtent, double containerExtent) {
    setState(() {
      scrollTo(scrollBehavior.updateExtents(
        contentExtent: contentExtent,
        containerExtent: containerExtent,
        scrollOffset: scrollOffset
      ));
    });
  }

  Widget buildContent(BuildContext context) {
    return new GridViewport(
      startOffset: scrollOffset,
      delegate: config.delegate,
      onExtentsChanged: _handleExtentsChanged,
      children: config.children
    );
  }
}

66
class GridViewport extends VirtualViewport with VirtualViewportIterableMixin {
67 68 69 70 71 72 73 74 75 76
  GridViewport({
    this.startOffset,
    this.delegate,
    this.onExtentsChanged,
    this.children
  });

  final double startOffset;
  final GridDelegate delegate;
  final ExtentsChangedCallback onExtentsChanged;
77
  final Iterable<Widget> children;
78

79
  // TODO(abarth): Support horizontal scrolling;
80
  Axis get scrollDirection => Axis.vertical;
81

82 83 84 85 86 87
  RenderGrid createRenderObject() => new RenderGrid(delegate: delegate);

  _GridViewportElement createElement() => new _GridViewportElement(this);
}

// TODO(abarth): This function should go somewhere more general.
Adam Barth's avatar
Adam Barth committed
88
// See https://github.com/dart-lang/collection/pull/16
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
int _lowerBound(List sortedList, var value, { int begin: 0 }) {
  int current = begin;
  int count = sortedList.length - current;
  while (count > 0) {
    int step = count >> 1;
    int test = current + step;
    if (sortedList[test] < value) {
      current = test + 1;
      count -= step + 1;
    } else {
      count = step;
    }
  }
  return current;
}

Adam Barth's avatar
Adam Barth committed
105
class _GridViewportElement extends VirtualViewportElement<GridViewport> {
106 107
  _GridViewportElement(GridViewport widget) : super(widget);

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

Adam Barth's avatar
Adam Barth committed
110
  int get materializedChildBase => _materializedChildBase;
111 112
  int _materializedChildBase;

Adam Barth's avatar
Adam Barth committed
113 114
  int get materializedChildCount => _materializedChildCount;
  int _materializedChildCount;
115

116 117
  double get startOffsetBase => _startOffsetBase;
  double _startOffsetBase;
118

119 120
  double get startOffsetLimit =>_startOffsetLimit;
  double _startOffsetLimit;
121

122
  void updateRenderObject(GridViewport oldWidget) {
123
    renderObject.delegate = widget.delegate;
124
    super.updateRenderObject(oldWidget);
125 126
  }

Adam Barth's avatar
Adam Barth committed
127 128 129
  double _contentExtent;
  double _containerExtent;
  GridSpecification _specification;
130 131 132 133 134 135 136 137 138

  void layout(BoxConstraints constraints) {
    _specification = renderObject.specification;
    double contentExtent = _specification.gridSize.height;
    double containerExtent = renderObject.size.height;

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

139 140
    _materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
    _materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
141 142
    _startOffsetBase = _specification.rowOffsets[materializedRowBase];
    _startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
143

Adam Barth's avatar
Adam Barth committed
144
    super.layout(constraints);
145 146 147 148 149 150 151 152

    if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
      _contentExtent = contentExtent;
      _containerExtent = containerExtent;
      widget.onExtentsChanged(_contentExtent, _containerExtent);
    }
  }
}