Merge pull request #1070 from abarth/scrollable_list2
Introduce ScrollableList2
This commit is contained in:
commit
24fadf49fc
@ -72,13 +72,14 @@ class MediaQueryExample extends StatelessComponent {
|
|||||||
items.add(new AdaptiveItem("Item $i"));
|
items.add(new AdaptiveItem("Item $i"));
|
||||||
|
|
||||||
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
|
if (MediaQuery.of(context).size.width < _gridViewBreakpoint) {
|
||||||
return new Block(
|
return new ScrollableList2(
|
||||||
items.map((AdaptiveItem item) => item.toListItem()).toList()
|
itemExtent: 50.0,
|
||||||
|
children: items.map((AdaptiveItem item) => item.toListItem()).toList()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new ScrollableGrid(
|
return new ScrollableGrid(
|
||||||
children: items.map((AdaptiveItem item) => item.toCard()).toList(),
|
delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth),
|
||||||
delegate: new MaxTileWidthGridDelegate(maxTileWidth: _maxTileWidth)
|
children: items.map((AdaptiveItem item) => item.toCard()).toList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ export 'src/rendering/flex.dart';
|
|||||||
export 'src/rendering/grid.dart';
|
export 'src/rendering/grid.dart';
|
||||||
export 'src/rendering/image.dart';
|
export 'src/rendering/image.dart';
|
||||||
export 'src/rendering/layer.dart';
|
export 'src/rendering/layer.dart';
|
||||||
|
export 'src/rendering/list.dart';
|
||||||
export 'src/rendering/node.dart';
|
export 'src/rendering/node.dart';
|
||||||
export 'src/rendering/object.dart';
|
export 'src/rendering/object.dart';
|
||||||
export 'src/rendering/overflow.dart';
|
export 'src/rendering/overflow.dart';
|
||||||
|
@ -6,8 +6,7 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
import 'viewport.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
|
||||||
|
|
||||||
bool _debugIsMonotonic(List<double> offsets) {
|
bool _debugIsMonotonic(List<double> offsets) {
|
||||||
bool result = true;
|
bool result = true;
|
||||||
@ -314,8 +313,7 @@ class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {
|
|||||||
///
|
///
|
||||||
/// Additionally, grid layout materializes all of its children, which makes it
|
/// Additionally, grid layout materializes all of its children, which makes it
|
||||||
/// most useful for grids containing a moderate number of tiles.
|
/// most useful for grids containing a moderate number of tiles.
|
||||||
class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>,
|
class RenderGrid extends RenderVirtualViewport<GridParentData> {
|
||||||
RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
|
|
||||||
RenderGrid({
|
RenderGrid({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
GridDelegate delegate,
|
GridDelegate delegate,
|
||||||
@ -323,11 +321,11 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
|
|||||||
int virtualChildCount,
|
int virtualChildCount,
|
||||||
Offset paintOffset: Offset.zero,
|
Offset paintOffset: Offset.zero,
|
||||||
LayoutCallback callback
|
LayoutCallback callback
|
||||||
}) : _delegate = delegate,
|
}) : _delegate = delegate, _virtualChildBase = virtualChildBase, super(
|
||||||
_virtualChildBase = virtualChildBase,
|
virtualChildCount: virtualChildCount,
|
||||||
_virtualChildCount = virtualChildCount,
|
paintOffset: paintOffset,
|
||||||
_paintOffset = paintOffset,
|
callback: callback
|
||||||
_callback = callback {
|
) {
|
||||||
assert(delegate != null);
|
assert(delegate != null);
|
||||||
addAll(children);
|
addAll(children);
|
||||||
}
|
}
|
||||||
@ -360,49 +358,6 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The total number of virtual children in the grid.
|
|
||||||
///
|
|
||||||
/// When asking the delegate for the grid specification, the grid will use
|
|
||||||
/// this number of children, which can be larger than the actual number of
|
|
||||||
/// children of this render object.
|
|
||||||
///
|
|
||||||
/// If the this value is null, the grid will use the actual child count of
|
|
||||||
/// this render object.
|
|
||||||
int get virtualChildCount => _virtualChildCount ?? childCount;
|
|
||||||
int _virtualChildCount;
|
|
||||||
void set virtualChildCount(int value) {
|
|
||||||
if (_virtualChildCount == value)
|
|
||||||
return;
|
|
||||||
_virtualChildCount = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The offset at which to paint the first tile.
|
|
||||||
///
|
|
||||||
/// Note: you can modify this property from within [callback], if necessary.
|
|
||||||
Offset get paintOffset => _paintOffset;
|
|
||||||
Offset _paintOffset;
|
|
||||||
void set paintOffset(Offset value) {
|
|
||||||
assert(value != null);
|
|
||||||
if (value == _paintOffset)
|
|
||||||
return;
|
|
||||||
_paintOffset = value;
|
|
||||||
markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called during [layout] to determine the grid's children.
|
|
||||||
///
|
|
||||||
/// Typically the callback will mutate the child list appropriately, for
|
|
||||||
/// example so the child list contains only visible children.
|
|
||||||
LayoutCallback get callback => _callback;
|
|
||||||
LayoutCallback _callback;
|
|
||||||
void set callback(LayoutCallback value) {
|
|
||||||
if (value == _callback)
|
|
||||||
return;
|
|
||||||
_callback = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupParentData(RenderBox child) {
|
void setupParentData(RenderBox child) {
|
||||||
if (child.parentData is! GridParentData)
|
if (child.parentData is! GridParentData)
|
||||||
child.parentData = new GridParentData();
|
child.parentData = new GridParentData();
|
||||||
@ -447,17 +402,13 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _hasVisualOverflow = false;
|
|
||||||
|
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
_updateGridSpecification();
|
_updateGridSpecification();
|
||||||
Size gridSize = _specification.gridSize;
|
Size gridSize = _specification.gridSize;
|
||||||
size = constraints.constrain(gridSize);
|
size = constraints.constrain(gridSize);
|
||||||
if (gridSize.width > size.width || gridSize.height > size.height)
|
|
||||||
_hasVisualOverflow = true;
|
|
||||||
|
|
||||||
if (_callback != null)
|
if (callback != null)
|
||||||
invokeLayoutCallback(_callback);
|
invokeLayoutCallback(callback);
|
||||||
|
|
||||||
double gridTopPadding = _specification.padding.top;
|
double gridTopPadding = _specification.padding.top;
|
||||||
double gridLeftPadding = _specification.padding.left;
|
double gridLeftPadding = _specification.padding.left;
|
||||||
@ -492,29 +443,10 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
|
|||||||
tileTop + placement.padding.top
|
tileTop + placement.padding.top
|
||||||
);
|
);
|
||||||
|
|
||||||
++childIndex;
|
childIndex += 1;
|
||||||
|
|
||||||
assert(child.parentData == childParentData);
|
assert(child.parentData == childParentData);
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
|
||||||
super.applyPaintTransform(child, transform.translate(paintOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hitTestChildren(HitTestResult result, { Point position }) {
|
|
||||||
return defaultHitTestChildren(result, position: position + -paintOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _paintContents(PaintingContext context, Offset offset) {
|
|
||||||
defaultPaint(context, offset + paintOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
if (_hasVisualOverflow)
|
|
||||||
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
|
|
||||||
else
|
|
||||||
_paintContents(context, offset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
86
packages/flutter/lib/src/rendering/list.dart
Normal file
86
packages/flutter/lib/src/rendering/list.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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 'box.dart';
|
||||||
|
import 'object.dart';
|
||||||
|
import 'viewport.dart';
|
||||||
|
|
||||||
|
/// Parent data for use with [RenderList].
|
||||||
|
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
||||||
|
|
||||||
|
class RenderList extends RenderVirtualViewport<ListParentData> {
|
||||||
|
RenderList({
|
||||||
|
List<RenderBox> children,
|
||||||
|
double itemExtent,
|
||||||
|
int virtualChildCount,
|
||||||
|
Offset paintOffset: Offset.zero,
|
||||||
|
LayoutCallback callback
|
||||||
|
}) : _itemExtent = itemExtent, super(
|
||||||
|
virtualChildCount: virtualChildCount,
|
||||||
|
paintOffset: paintOffset,
|
||||||
|
callback: callback
|
||||||
|
) {
|
||||||
|
assert(itemExtent != null);
|
||||||
|
addAll(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
double get itemExtent => _itemExtent;
|
||||||
|
double _itemExtent;
|
||||||
|
void set itemExtent (double newValue) {
|
||||||
|
assert(newValue != null);
|
||||||
|
if (_itemExtent == newValue)
|
||||||
|
return;
|
||||||
|
_itemExtent = newValue;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupParentData(RenderBox child) {
|
||||||
|
if (child.parentData is! ListParentData)
|
||||||
|
child.parentData = new ListParentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
double get _preferredMainAxisExtent => itemExtent * virtualChildCount;
|
||||||
|
|
||||||
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
return constraints.constrainWidth(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
return constraints.constrainWidth(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
return constraints.constrainHeight(_preferredMainAxisExtent);
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
return constraints.constrainHeight(_preferredMainAxisExtent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void performLayout() {
|
||||||
|
double height = _preferredMainAxisExtent;
|
||||||
|
size = new Size(constraints.maxWidth, constraints.constrainHeight(height));
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
invokeLayoutCallback(callback);
|
||||||
|
|
||||||
|
BoxConstraints innerConstraints =
|
||||||
|
new BoxConstraints.tightFor(width: size.width, height: itemExtent);
|
||||||
|
|
||||||
|
int childIndex = 0;
|
||||||
|
RenderBox child = firstChild;
|
||||||
|
while (child != null) {
|
||||||
|
child.layout(innerConstraints);
|
||||||
|
final ListParentData childParentData = child.parentData;
|
||||||
|
childParentData.offset = new Offset(0.0, childIndex * itemExtent);
|
||||||
|
childIndex += 1;
|
||||||
|
assert(child.parentData == childParentData);
|
||||||
|
child = childParentData.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -178,3 +178,66 @@ class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<RenderBox>>
|
||||||
|
extends RenderBox with ContainerRenderObjectMixin<RenderBox, T>,
|
||||||
|
RenderBoxContainerDefaultsMixin<RenderBox, T> {
|
||||||
|
RenderVirtualViewport({
|
||||||
|
int virtualChildCount,
|
||||||
|
Offset paintOffset,
|
||||||
|
LayoutCallback callback
|
||||||
|
}) : _virtualChildCount = virtualChildCount,
|
||||||
|
_paintOffset = paintOffset,
|
||||||
|
_callback = callback;
|
||||||
|
|
||||||
|
int get virtualChildCount => _virtualChildCount ?? childCount;
|
||||||
|
int _virtualChildCount;
|
||||||
|
void set virtualChildCount(int value) {
|
||||||
|
if (_virtualChildCount == value)
|
||||||
|
return;
|
||||||
|
_virtualChildCount = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The offset at which to paint the first item.
|
||||||
|
///
|
||||||
|
/// Note: you can modify this property from within [callback], if necessary.
|
||||||
|
Offset get paintOffset => _paintOffset;
|
||||||
|
Offset _paintOffset;
|
||||||
|
void set paintOffset(Offset value) {
|
||||||
|
assert(value != null);
|
||||||
|
if (value == _paintOffset)
|
||||||
|
return;
|
||||||
|
_paintOffset = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called during [layout] to determine the grid's children.
|
||||||
|
///
|
||||||
|
/// Typically the callback will mutate the child list appropriately, for
|
||||||
|
/// example so the child list contains only visible children.
|
||||||
|
LayoutCallback get callback => _callback;
|
||||||
|
LayoutCallback _callback;
|
||||||
|
void set callback(LayoutCallback value) {
|
||||||
|
if (value == _callback)
|
||||||
|
return;
|
||||||
|
_callback = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
||||||
|
super.applyPaintTransform(child, transform.translate(paintOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||||
|
return defaultHitTestChildren(result, position: position + -paintOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _paintContents(PaintingContext context, Offset offset) {
|
||||||
|
defaultPaint(context, offset + paintOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'basic.dart';
|
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
|
import 'virtual_viewport.dart';
|
||||||
|
|
||||||
import 'package:flutter/animation.dart';
|
import 'package:flutter/animation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -38,11 +38,11 @@ class ScrollableGrid extends Scrollable {
|
|||||||
final GridDelegate delegate;
|
final GridDelegate delegate;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
ScrollableState createState() => new _ScrollableGrid();
|
ScrollableState createState() => new _ScrollableGridState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScrollableGrid extends ScrollableState<ScrollableGrid> {
|
class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
|
||||||
ScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior();
|
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
|
||||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||||
|
|
||||||
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
||||||
@ -65,9 +65,7 @@ class _ScrollableGrid extends ScrollableState<ScrollableGrid> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void ExtentsChangedCallback(double contentExtent, double containerExtent);
|
class GridViewport extends VirtualViewport {
|
||||||
|
|
||||||
class GridViewport extends RenderObjectWidget {
|
|
||||||
GridViewport({
|
GridViewport({
|
||||||
Key key,
|
Key key,
|
||||||
this.startOffset,
|
this.startOffset,
|
||||||
@ -87,6 +85,7 @@ class GridViewport extends RenderObjectWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(abarth): This function should go somewhere more general.
|
// TODO(abarth): This function should go somewhere more general.
|
||||||
|
// See https://github.com/dart-lang/collection/pull/16
|
||||||
int _lowerBound(List sortedList, var value, { int begin: 0 }) {
|
int _lowerBound(List sortedList, var value, { int begin: 0 }) {
|
||||||
int current = begin;
|
int current = begin;
|
||||||
int count = sortedList.length - current;
|
int count = sortedList.length - current;
|
||||||
@ -103,82 +102,31 @@ int _lowerBound(List sortedList, var value, { int begin: 0 }) {
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridViewportElement extends RenderObjectElement<GridViewport> {
|
class _GridViewportElement extends VirtualViewportElement<GridViewport> {
|
||||||
_GridViewportElement(GridViewport widget) : super(widget);
|
_GridViewportElement(GridViewport widget) : super(widget);
|
||||||
|
|
||||||
double _contentExtent;
|
|
||||||
double _containerExtent;
|
|
||||||
|
|
||||||
int _materializedChildBase;
|
|
||||||
int _materializedChildCount;
|
|
||||||
|
|
||||||
List<Element> _materializedChildren = const <Element>[];
|
|
||||||
|
|
||||||
GridSpecification _specification;
|
|
||||||
double _repaintOffsetBase;
|
|
||||||
double _repaintOffsetLimit;
|
|
||||||
|
|
||||||
RenderGrid get renderObject => super.renderObject;
|
RenderGrid get renderObject => super.renderObject;
|
||||||
|
|
||||||
void visitChildren(ElementVisitor visitor) {
|
int get materializedChildBase => _materializedChildBase;
|
||||||
if (_materializedChildren == null)
|
int _materializedChildBase;
|
||||||
return;
|
|
||||||
for (Element child in _materializedChildren)
|
|
||||||
visitor(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
void mount(Element parent, dynamic newSlot) {
|
int get materializedChildCount => _materializedChildCount;
|
||||||
super.mount(parent, newSlot);
|
int _materializedChildCount;
|
||||||
renderObject.callback = layout;
|
|
||||||
_updateRenderObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void unmount() {
|
double get repaintOffsetBase => _repaintOffsetBase;
|
||||||
renderObject.callback = null;
|
double _repaintOffsetBase;
|
||||||
super.unmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(GridViewport newWidget) {
|
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
||||||
super.update(newWidget);
|
double _repaintOffsetLimit;
|
||||||
_updateRenderObject();
|
|
||||||
if (!renderObject.needsLayout)
|
|
||||||
_materializeChildren();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updatePaintOffset() {
|
void updateRenderObject() {
|
||||||
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - _repaintOffsetBase));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateRenderObject() {
|
|
||||||
renderObject.delegate = widget.delegate;
|
renderObject.delegate = widget.delegate;
|
||||||
renderObject.virtualChildCount = widget.children.length;
|
super.updateRenderObject();
|
||||||
|
|
||||||
if (_specification != null) {
|
|
||||||
_updatePaintOffset();
|
|
||||||
|
|
||||||
// If we don't already need layout, we need to request a layout if the
|
|
||||||
// viewport has shifted to expose a new row.
|
|
||||||
if (!renderObject.needsLayout) {
|
|
||||||
if (_repaintOffsetBase != null && widget.startOffset < _repaintOffsetBase)
|
|
||||||
renderObject.markNeedsLayout();
|
|
||||||
else if (_repaintOffsetLimit != null && widget.startOffset + _containerExtent > _repaintOffsetLimit)
|
|
||||||
renderObject.markNeedsLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _materializeChildren() {
|
double _contentExtent;
|
||||||
assert(_materializedChildBase != null);
|
double _containerExtent;
|
||||||
assert(_materializedChildCount != null);
|
GridSpecification _specification;
|
||||||
List<Widget> newWidgets = new List<Widget>(_materializedChildCount);
|
|
||||||
for (int i = 0; i < _materializedChildCount; ++i) {
|
|
||||||
int childIndex = _materializedChildBase + i;
|
|
||||||
Widget child = widget.children[childIndex];
|
|
||||||
Key key = new ValueKey(child.key ?? childIndex);
|
|
||||||
newWidgets[i] = new RepaintBoundary(key: key, child: child);
|
|
||||||
}
|
|
||||||
_materializedChildren = updateChildren(_materializedChildren, newWidgets);
|
|
||||||
}
|
|
||||||
|
|
||||||
void layout(BoxConstraints constraints) {
|
void layout(BoxConstraints constraints) {
|
||||||
_specification = renderObject.specification;
|
_specification = renderObject.specification;
|
||||||
@ -188,13 +136,12 @@ class _GridViewportElement extends RenderObjectElement<GridViewport> {
|
|||||||
int materializedRowBase = math.max(0, _lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
|
int materializedRowBase = math.max(0, _lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
|
||||||
int materializedRowLimit = math.min(_specification.rowCount, _lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
|
int materializedRowLimit = math.min(_specification.rowCount, _lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
|
||||||
|
|
||||||
_materializedChildBase = materializedRowBase * _specification.columnCount;
|
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, widget.children.length);
|
||||||
_materializedChildCount = math.min(widget.children.length, materializedRowLimit * _specification.columnCount) - _materializedChildBase;
|
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, widget.children.length) - _materializedChildBase;
|
||||||
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
_repaintOffsetBase = _specification.rowOffsets[materializedRowBase];
|
||||||
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit];
|
_repaintOffsetLimit = _specification.rowOffsets[materializedRowLimit];
|
||||||
_updatePaintOffset();
|
|
||||||
|
|
||||||
BuildableElement.lockState(_materializeChildren);
|
super.layout(constraints);
|
||||||
|
|
||||||
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
|
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
|
||||||
_contentExtent = contentExtent;
|
_contentExtent = contentExtent;
|
||||||
@ -202,20 +149,4 @@ class _GridViewportElement extends RenderObjectElement<GridViewport> {
|
|||||||
widget.onExtentsChanged(_contentExtent, _containerExtent);
|
widget.onExtentsChanged(_contentExtent, _containerExtent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
124
packages/flutter/lib/src/widgets/scrollable_list.dart
Normal file
124
packages/flutter/lib/src/widgets/scrollable_list.dart
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// 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 'framework.dart';
|
||||||
|
import 'scrollable.dart';
|
||||||
|
import 'virtual_viewport.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
class ScrollableList2 extends Scrollable {
|
||||||
|
ScrollableList2({
|
||||||
|
Key key,
|
||||||
|
double initialScrollOffset,
|
||||||
|
ScrollListener onScroll,
|
||||||
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
|
double snapAlignmentOffset: 0.0,
|
||||||
|
this.itemExtent,
|
||||||
|
this.children
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
initialScrollOffset: initialScrollOffset,
|
||||||
|
// TODO(abarth): Support horizontal offsets.
|
||||||
|
scrollDirection: ScrollDirection.vertical,
|
||||||
|
onScroll: onScroll,
|
||||||
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
|
snapAlignmentOffset: snapAlignmentOffset
|
||||||
|
);
|
||||||
|
|
||||||
|
final double itemExtent;
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
ScrollableState createState() => new _ScrollableList2State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollableList2State extends ScrollableState<ScrollableList2> {
|
||||||
|
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
|
||||||
|
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 ListViewport(
|
||||||
|
startOffset: scrollOffset,
|
||||||
|
itemExtent: config.itemExtent,
|
||||||
|
onExtentsChanged: _handleExtentsChanged,
|
||||||
|
children: config.children
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListViewport extends VirtualViewport {
|
||||||
|
ListViewport({
|
||||||
|
Key key,
|
||||||
|
this.startOffset,
|
||||||
|
this.itemExtent,
|
||||||
|
this.onExtentsChanged,
|
||||||
|
this.children
|
||||||
|
});
|
||||||
|
|
||||||
|
final double startOffset;
|
||||||
|
final double itemExtent;
|
||||||
|
final ExtentsChangedCallback onExtentsChanged;
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
||||||
|
|
||||||
|
_ListViewportElement createElement() => new _ListViewportElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
||||||
|
_ListViewportElement(ListViewport widget) : super(widget);
|
||||||
|
|
||||||
|
RenderList get renderObject => super.renderObject;
|
||||||
|
|
||||||
|
int get materializedChildBase => _materializedChildBase;
|
||||||
|
int _materializedChildBase;
|
||||||
|
|
||||||
|
int get materializedChildCount => _materializedChildCount;
|
||||||
|
int _materializedChildCount;
|
||||||
|
|
||||||
|
double get repaintOffsetBase => _repaintOffsetBase;
|
||||||
|
double _repaintOffsetBase;
|
||||||
|
|
||||||
|
double get repaintOffsetLimit =>_repaintOffsetLimit;
|
||||||
|
double _repaintOffsetLimit;
|
||||||
|
|
||||||
|
void updateRenderObject() {
|
||||||
|
renderObject.itemExtent = widget.itemExtent;
|
||||||
|
super.updateRenderObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _contentExtent;
|
||||||
|
double _containerExtent;
|
||||||
|
|
||||||
|
void layout(BoxConstraints constraints) {
|
||||||
|
double contentExtent = widget.itemExtent * widget.children.length;
|
||||||
|
double containerExtent = renderObject.size.height;
|
||||||
|
|
||||||
|
_materializedChildBase = (widget.startOffset ~/ widget.itemExtent).clamp(0, widget.children.length);
|
||||||
|
int materializedChildLimit = ((widget.startOffset + containerExtent) / widget.itemExtent).ceil().clamp(0, widget.children.length);
|
||||||
|
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||||
|
|
||||||
|
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
||||||
|
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
|
||||||
|
|
||||||
|
super.layout(constraints);
|
||||||
|
|
||||||
|
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
|
||||||
|
_contentExtent = contentExtent;
|
||||||
|
_containerExtent = containerExtent;
|
||||||
|
widget.onExtentsChanged(_contentExtent, _containerExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
packages/flutter/lib/src/widgets/virtual_viewport.dart
Normal file
113
packages/flutter/lib/src/widgets/virtual_viewport.dart
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// 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 '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;
|
||||||
|
List<Widget> get children;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderObjectElement<T> {
|
||||||
|
VirtualViewportElement(T widget) : super(widget);
|
||||||
|
|
||||||
|
int get materializedChildBase;
|
||||||
|
int get materializedChildCount;
|
||||||
|
double get repaintOffsetBase;
|
||||||
|
double get repaintOffsetLimit;
|
||||||
|
|
||||||
|
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);
|
||||||
|
renderObject.callback = layout;
|
||||||
|
updateRenderObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unmount() {
|
||||||
|
renderObject.callback = null;
|
||||||
|
super.unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(T newWidget) {
|
||||||
|
super.update(newWidget);
|
||||||
|
updateRenderObject();
|
||||||
|
if (!renderObject.needsLayout)
|
||||||
|
_materializeChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updatePaintOffset() {
|
||||||
|
renderObject.paintOffset =
|
||||||
|
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateRenderObject() {
|
||||||
|
renderObject.virtualChildCount = widget.children.length;
|
||||||
|
|
||||||
|
if (repaintOffsetBase != null) {
|
||||||
|
_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) {
|
||||||
|
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
||||||
|
renderObject.markNeedsLayout();
|
||||||
|
else if (repaintOffsetLimit != null && widget.startOffset + renderObject.size.height > repaintOffsetLimit)
|
||||||
|
renderObject.markNeedsLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void layout(BoxConstraints constraints) {
|
||||||
|
assert(repaintOffsetBase != null);
|
||||||
|
assert(repaintOffsetLimit != null);
|
||||||
|
_updatePaintOffset();
|
||||||
|
BuildableElement.lockState(_materializeChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _materializeChildren() {
|
||||||
|
int base = materializedChildBase;
|
||||||
|
int count = materializedChildCount;
|
||||||
|
assert(base != null);
|
||||||
|
assert(count != null);
|
||||||
|
List<Widget> newWidgets = new List<Widget>(count);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
int childIndex = base + i;
|
||||||
|
Widget child = widget.children[childIndex];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -33,10 +33,12 @@ export 'src/widgets/placeholder.dart';
|
|||||||
export 'src/widgets/routes.dart';
|
export 'src/widgets/routes.dart';
|
||||||
export 'src/widgets/scrollable.dart';
|
export 'src/widgets/scrollable.dart';
|
||||||
export 'src/widgets/scrollable_grid.dart';
|
export 'src/widgets/scrollable_grid.dart';
|
||||||
|
export 'src/widgets/scrollable_list.dart';
|
||||||
export 'src/widgets/statistics_overlay.dart';
|
export 'src/widgets/statistics_overlay.dart';
|
||||||
export 'src/widgets/status_transitions.dart';
|
export 'src/widgets/status_transitions.dart';
|
||||||
export 'src/widgets/title.dart';
|
export 'src/widgets/title.dart';
|
||||||
export 'src/widgets/transitions.dart';
|
export 'src/widgets/transitions.dart';
|
||||||
export 'src/widgets/unique_component.dart';
|
export 'src/widgets/unique_component.dart';
|
||||||
|
export 'src/widgets/virtual_viewport.dart';
|
||||||
|
|
||||||
export 'package:vector_math/vector_math_64.dart' show Matrix4;
|
export 'package:vector_math/vector_math_64.dart' show Matrix4;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user