virtual_viewport.dart 4.85 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4
// 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.

5 6
import 'dart:math' as math;

Adam Barth's avatar
Adam Barth committed
7 8 9 10 11 12 13 14 15
import 'basic.dart';
import 'framework.dart';

import 'package:flutter/rendering.dart';

typedef void ExtentsChangedCallback(double contentExtent, double containerExtent);

abstract class VirtualViewport extends RenderObjectWidget {
  double get startOffset;
16
  Axis get scrollDirection;
17
  Iterable<Widget> get children;
Adam Barth's avatar
Adam Barth committed
18 19 20 21 22 23 24
}

abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
  VirtualViewportElement(T widget) : super(widget);

  int get materializedChildBase;
  int get materializedChildCount;
25 26 27 28
  double get startOffsetBase;
  double get startOffsetLimit;

  double get paintOffset => -(widget.startOffset - startOffsetBase);
Adam Barth's avatar
Adam Barth committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42

  List<Element> _materializedChildren = const <Element>[];

  RenderVirtualViewport get renderObject => super.renderObject;

  void visitChildren(ElementVisitor visitor) {
    if (_materializedChildren == null)
      return;
    for (Element child in _materializedChildren)
      visitor(child);
  }

  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
43 44
    _iterator = null;
    _widgets = <Widget>[];
Adam Barth's avatar
Adam Barth committed
45
    renderObject.callback = layout;
46
    updateRenderObject(null);
Adam Barth's avatar
Adam Barth committed
47 48 49 50 51 52 53 54
  }

  void unmount() {
    renderObject.callback = null;
    super.unmount();
  }

  void update(T newWidget) {
55 56 57 58
    if (widget.children != newWidget.children) {
      _iterator = null;
      _widgets = <Widget>[];
    }
59
    T oldWidget = widget;
Adam Barth's avatar
Adam Barth committed
60
    super.update(newWidget);
61
    updateRenderObject(oldWidget);
Adam Barth's avatar
Adam Barth committed
62 63 64 65 66
    if (!renderObject.needsLayout)
      _materializeChildren();
  }

  void _updatePaintOffset() {
67
    switch (widget.scrollDirection) {
68
      case Axis.vertical:
69
        renderObject.paintOffset = new Offset(0.0, paintOffset);
70
        break;
71
      case Axis.horizontal:
72
        renderObject.paintOffset = new Offset(paintOffset, 0.0);
73 74 75 76
        break;
    }
  }

77
  void updateRenderObject(T oldWidget) {
Adam Barth's avatar
Adam Barth committed
78 79
    renderObject.virtualChildCount = widget.children.length;

80
    if (startOffsetBase != null) {
Adam Barth's avatar
Adam Barth committed
81 82 83 84 85
      _updatePaintOffset();

      // If we don't already need layout, we need to request a layout if the
      // viewport has shifted to expose new children.
      if (!renderObject.needsLayout) {
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
        bool shouldLayout = false;
        if (startOffsetBase != null) {
          if (widget.startOffset < startOffsetBase)
            shouldLayout = true;
          else if (widget.startOffset == startOffsetBase && oldWidget?.startOffset != startOffsetBase)
            shouldLayout = true;
        }

        if (startOffsetLimit != null) {
          if (widget.startOffset > startOffsetLimit)
            shouldLayout = true;
          else if (widget.startOffset == startOffsetLimit && oldWidget?.startOffset != startOffsetLimit)
            shouldLayout = true;
        }

        if (shouldLayout)
Adam Barth's avatar
Adam Barth committed
102 103 104 105 106 107
          renderObject.markNeedsLayout();
      }
    }
  }

  void layout(BoxConstraints constraints) {
108 109
    assert(startOffsetBase != null);
    assert(startOffsetLimit != null);
Adam Barth's avatar
Adam Barth committed
110
    _updatePaintOffset();
111
    BuildableElement.lockState(_materializeChildren, building: true);
Adam Barth's avatar
Adam Barth committed
112 113
  }

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
  Iterator<Widget> _iterator;
  List<Widget> _widgets;

  void _populateWidgets(int limit) {
    if (limit <= _widgets.length)
      return;
    if (widget.children is List<Widget>) {
      _widgets = widget.children;
      return;
    }
    _iterator ??= widget.children.iterator;
    while (_widgets.length < limit) {
      bool moved = _iterator.moveNext();
      assert(moved);
      Widget current = _iterator.current;
      assert(current != null);
      _widgets.add(current);
    }
  }

Adam Barth's avatar
Adam Barth committed
134 135 136
  void _materializeChildren() {
    int base = materializedChildBase;
    int count = materializedChildCount;
137
    int length = renderObject.virtualChildCount;
Adam Barth's avatar
Adam Barth committed
138 139
    assert(base != null);
    assert(count != null);
140
    _populateWidgets(base < 0 ? length : math.min(length, base + count));
Adam Barth's avatar
Adam Barth committed
141 142 143
    List<Widget> newWidgets = new List<Widget>(count);
    for (int i = 0; i < count; ++i) {
      int childIndex = base + i;
144
      Widget child = _widgets[(childIndex % length).abs()];
Adam Barth's avatar
Adam Barth committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
      Key key = new ValueKey(child.key ?? childIndex);
      newWidgets[i] = new RepaintBoundary(key: key, child: child);
    }
    _materializedChildren = updateChildren(_materializedChildren, newWidgets);
  }

  void insertChildRenderObject(RenderObject child, Element slot) {
    RenderObject nextSibling = slot?.renderObject;
    renderObject.add(child, before: nextSibling);
  }

  void moveChildRenderObject(RenderObject child, Element slot) {
    assert(child.parent == renderObject);
    RenderObject nextSibling = slot?.renderObject;
    renderObject.move(child, before: nextSibling);
  }

  void removeChildRenderObject(RenderObject child) {
    assert(child.parent == renderObject);
    renderObject.remove(child);
  }
}