Complete features of ScrollableList2
This patch implements the remaining missing features of ScrollableList2. It should now be nearly a drop-in replacement for ScrollableList. The next patch will switch callers over to the new machinery.
This commit is contained in:
parent
f72c8f6db7
commit
37106ea603
@ -30,7 +30,8 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
Point thumbOrigin;
|
Point thumbOrigin;
|
||||||
Size thumbSize;
|
Size thumbSize;
|
||||||
|
|
||||||
if (isVertical) {
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
|
double thumbHeight = viewportBounds.height * viewportBounds.height / contentExtent;
|
||||||
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
|
thumbHeight = thumbHeight.clamp(_kMinScrollbarThumbLength, viewportBounds.height);
|
||||||
final double maxThumbTop = viewportBounds.height - thumbHeight;
|
final double maxThumbTop = viewportBounds.height - thumbHeight;
|
||||||
@ -38,7 +39,8 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
|
thumbTop = viewportBounds.top + thumbTop.clamp(0.0, maxThumbTop);
|
||||||
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
|
thumbOrigin = new Point(viewportBounds.right - _kScrollbarThumbGirth, thumbTop);
|
||||||
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
|
thumbSize = new Size(_kScrollbarThumbGirth, thumbHeight);
|
||||||
} else {
|
break;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
|
double thumbWidth = viewportBounds.width * viewportBounds.width / contentExtent;
|
||||||
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
|
thumbWidth = thumbWidth.clamp(_kMinScrollbarThumbLength, viewportBounds.width);
|
||||||
final double maxThumbLeft = viewportBounds.width - thumbWidth;
|
final double maxThumbLeft = viewportBounds.width - thumbWidth;
|
||||||
@ -46,6 +48,7 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
thumbLeft = viewportBounds.left + thumbLeft.clamp(0.0, maxThumbLeft);
|
thumbLeft = viewportBounds.left + thumbLeft.clamp(0.0, maxThumbLeft);
|
||||||
thumbOrigin = new Point(thumbLeft, viewportBounds.height - _kScrollbarThumbGirth);
|
thumbOrigin = new Point(thumbLeft, viewportBounds.height - _kScrollbarThumbGirth);
|
||||||
thumbSize = new Size(thumbWidth, _kScrollbarThumbGirth);
|
thumbSize = new Size(thumbWidth, _kScrollbarThumbGirth);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
paintThumb(context, thumbOrigin & thumbSize);
|
paintThumb(context, thumbOrigin & thumbSize);
|
||||||
@ -65,7 +68,7 @@ class ScrollbarPainter extends ScrollableListPainter {
|
|||||||
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease)
|
..variable = new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
_opacity = _fade.value;
|
_opacity = _fade.value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
});
|
});
|
||||||
return _fade.forward();
|
return _fade.forward();
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
|
|||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
import 'viewport.dart';
|
||||||
|
|
||||||
/// Parent data for use with [RenderBlockBase].
|
/// Parent data for use with [RenderBlockBase].
|
||||||
class BlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
class BlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
||||||
@ -32,8 +33,10 @@ typedef double _Constrainer(double value);
|
|||||||
/// children. Because blocks expand in the main axis, blocks must be given
|
/// children. Because blocks expand in the main axis, blocks must be given
|
||||||
/// unlimited space in the main axis, typically by being contained in a
|
/// unlimited space in the main axis, typically by being contained in a
|
||||||
/// viewport with a scrolling direction that matches the block's main axis.
|
/// viewport with a scrolling direction that matches the block's main axis.
|
||||||
abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
|
abstract class RenderBlockBase extends RenderBox
|
||||||
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
|
with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
|
||||||
|
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData>
|
||||||
|
implements RenderScrollable {
|
||||||
|
|
||||||
RenderBlockBase({
|
RenderBlockBase({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
@ -82,6 +85,9 @@ abstract class RenderBlockBase extends RenderBox with ContainerRenderObjectMixin
|
|||||||
/// Whether the main axis is vertical.
|
/// Whether the main axis is vertical.
|
||||||
bool get isVertical => _direction == BlockDirection.vertical;
|
bool get isVertical => _direction == BlockDirection.vertical;
|
||||||
|
|
||||||
|
// TODO(abarth): Remove BlockDirection in favor of ScrollDirection.
|
||||||
|
ScrollDirection get scrollDirection => isVertical ? ScrollDirection.vertical : ScrollDirection.horizontal;
|
||||||
|
|
||||||
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
||||||
if (isVertical)
|
if (isVertical)
|
||||||
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth),
|
return new BoxConstraints.tightFor(width: constraints.constrainWidth(constraints.maxWidth),
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
import 'viewport.dart';
|
import 'viewport.dart';
|
||||||
@ -9,14 +11,19 @@ import 'viewport.dart';
|
|||||||
/// Parent data for use with [RenderList].
|
/// Parent data for use with [RenderList].
|
||||||
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
|
||||||
|
|
||||||
class RenderList extends RenderVirtualViewport<ListParentData> {
|
class RenderList extends RenderVirtualViewport<ListParentData> implements RenderScrollable {
|
||||||
RenderList({
|
RenderList({
|
||||||
List<RenderBox> children,
|
List<RenderBox> children,
|
||||||
double itemExtent,
|
double itemExtent,
|
||||||
|
EdgeDims padding,
|
||||||
int virtualChildCount,
|
int virtualChildCount,
|
||||||
Offset paintOffset: Offset.zero,
|
Offset paintOffset: Offset.zero,
|
||||||
|
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||||
LayoutCallback callback
|
LayoutCallback callback
|
||||||
}) : _itemExtent = itemExtent, super(
|
}) : _itemExtent = itemExtent,
|
||||||
|
_padding = padding,
|
||||||
|
_scrollDirection = scrollDirection,
|
||||||
|
super(
|
||||||
virtualChildCount: virtualChildCount,
|
virtualChildCount: virtualChildCount,
|
||||||
paintOffset: paintOffset,
|
paintOffset: paintOffset,
|
||||||
callback: callback
|
callback: callback
|
||||||
@ -35,50 +42,124 @@ class RenderList extends RenderVirtualViewport<ListParentData> {
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EdgeDims get padding => _padding;
|
||||||
|
EdgeDims _padding;
|
||||||
|
void set padding (EdgeDims newValue) {
|
||||||
|
if (_padding == newValue)
|
||||||
|
return;
|
||||||
|
_padding = newValue;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollDirection get scrollDirection => _scrollDirection;
|
||||||
|
ScrollDirection _scrollDirection;
|
||||||
|
void set scrollDirection (ScrollDirection newValue) {
|
||||||
|
if (_scrollDirection == newValue)
|
||||||
|
return;
|
||||||
|
_scrollDirection = newValue;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
void setupParentData(RenderBox child) {
|
void setupParentData(RenderBox child) {
|
||||||
if (child.parentData is! ListParentData)
|
if (child.parentData is! ListParentData)
|
||||||
child.parentData = new ListParentData();
|
child.parentData = new ListParentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _preferredMainAxisExtent => itemExtent * virtualChildCount;
|
double get _scrollAxisPadding {
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return padding.vertical;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return padding.horizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double get _preferredExtent {
|
||||||
|
double extent = itemExtent * virtualChildCount;
|
||||||
|
if (padding != null)
|
||||||
|
extent += _scrollAxisPadding;
|
||||||
|
return extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
assert(constraints.isNormalized);
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return constraints.constrainWidth(0.0);
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return constraints.constrainWidth(_preferredExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicWidth(constraints);
|
||||||
return constraints.constrainWidth(0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
return _getIntrinsicWidth(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
assert(constraints.isNormalized);
|
||||||
return constraints.constrainWidth(0.0);
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return constraints.constrainHeight(_preferredExtent);
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return constraints.constrainHeight(0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicHeight(constraints);
|
||||||
return constraints.constrainHeight(_preferredMainAxisExtent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||||
assert(constraints.isNormalized);
|
return _getIntrinsicHeight(constraints);
|
||||||
return constraints.constrainHeight(_preferredMainAxisExtent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
double height = _preferredMainAxisExtent;
|
size = new Size(constraints.maxWidth,
|
||||||
size = new Size(constraints.maxWidth, constraints.constrainHeight(height));
|
constraints.constrainHeight(_preferredExtent));
|
||||||
|
|
||||||
if (callback != null)
|
if (callback != null)
|
||||||
invokeLayoutCallback(callback);
|
invokeLayoutCallback(callback);
|
||||||
|
|
||||||
BoxConstraints innerConstraints =
|
double itemWidth;
|
||||||
new BoxConstraints.tightFor(width: size.width, height: itemExtent);
|
double itemHeight;
|
||||||
|
|
||||||
|
double x = 0.0;
|
||||||
|
double dx = 0.0;
|
||||||
|
|
||||||
|
double y = 0.0;
|
||||||
|
double dy = 0.0;
|
||||||
|
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
itemWidth = math.max(0, size.width - (padding == null ? 0.0 : padding.horizontal));
|
||||||
|
itemHeight = itemExtent;
|
||||||
|
y = padding != null ? padding.top : 0.0;
|
||||||
|
dy = itemExtent;
|
||||||
|
break;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
itemWidth = itemExtent;
|
||||||
|
itemHeight = math.max(0, size.height - (padding == null ? 0.0 : padding.vertical));
|
||||||
|
x = padding != null ? padding.left : 0.0;
|
||||||
|
dx = itemExtent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxConstraints innerConstraints =
|
||||||
|
new BoxConstraints.tightFor(width: itemWidth, height: itemHeight);
|
||||||
|
|
||||||
int childIndex = 0;
|
|
||||||
RenderBox child = firstChild;
|
RenderBox child = firstChild;
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
child.layout(innerConstraints);
|
child.layout(innerConstraints);
|
||||||
|
|
||||||
final ListParentData childParentData = child.parentData;
|
final ListParentData childParentData = child.parentData;
|
||||||
childParentData.offset = new Offset(0.0, childIndex * itemExtent);
|
childParentData.offset = new Offset(x, y);
|
||||||
childIndex += 1;
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
|
||||||
assert(child.parentData == childParentData);
|
assert(child.parentData == childParentData);
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ enum ScrollDirection {
|
|||||||
vertical,
|
vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class RenderScrollable {
|
||||||
|
ScrollDirection get scrollDirection;
|
||||||
|
}
|
||||||
|
|
||||||
/// A render object that's bigger on the inside.
|
/// A render object that's bigger on the inside.
|
||||||
///
|
///
|
||||||
/// The child of a viewport can layout to a larger size than the viewport
|
/// The child of a viewport can layout to a larger size than the viewport
|
||||||
@ -27,7 +31,8 @@ enum ScrollDirection {
|
|||||||
///
|
///
|
||||||
/// Viewport is the core scrolling primitive in the system, but it can be used
|
/// Viewport is the core scrolling primitive in the system, but it can be used
|
||||||
/// in other situations.
|
/// in other situations.
|
||||||
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox>
|
||||||
|
implements RenderScrollable {
|
||||||
|
|
||||||
RenderViewport({
|
RenderViewport({
|
||||||
RenderBox child,
|
RenderBox child,
|
||||||
@ -177,10 +182,12 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
RenderVirtualViewport({
|
RenderVirtualViewport({
|
||||||
int virtualChildCount,
|
int virtualChildCount,
|
||||||
Offset paintOffset,
|
Offset paintOffset,
|
||||||
LayoutCallback callback
|
LayoutCallback callback,
|
||||||
|
Painter overlayPainter
|
||||||
}) : _virtualChildCount = virtualChildCount,
|
}) : _virtualChildCount = virtualChildCount,
|
||||||
_paintOffset = paintOffset,
|
_paintOffset = paintOffset,
|
||||||
_callback = callback;
|
_callback = callback,
|
||||||
|
_overlayPainter = overlayPainter;
|
||||||
|
|
||||||
int get virtualChildCount => _virtualChildCount ?? childCount;
|
int get virtualChildCount => _virtualChildCount ?? childCount;
|
||||||
int _virtualChildCount;
|
int _virtualChildCount;
|
||||||
@ -217,8 +224,31 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Painter get overlayPainter => _overlayPainter;
|
||||||
|
Painter _overlayPainter;
|
||||||
|
void set overlayPainter(Painter value) {
|
||||||
|
if (_overlayPainter == value)
|
||||||
|
return;
|
||||||
|
if (attached)
|
||||||
|
_overlayPainter?.detach();
|
||||||
|
_overlayPainter = value;
|
||||||
|
if (attached)
|
||||||
|
_overlayPainter?.attach(this);
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void attach() {
|
||||||
|
super.attach();
|
||||||
|
_overlayPainter?.attach(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void detach() {
|
||||||
|
super.detach();
|
||||||
|
_overlayPainter?.detach();
|
||||||
|
}
|
||||||
|
|
||||||
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
||||||
super.applyPaintTransform(child, transform.translate(paintOffset));
|
super.applyPaintTransform(child, transform.translate(paintOffset.dx, paintOffset.dy));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hitTestChildren(HitTestResult result, { Point position }) {
|
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||||
@ -227,6 +257,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
|
|||||||
|
|
||||||
void _paintContents(PaintingContext context, Offset offset) {
|
void _paintContents(PaintingContext context, Offset offset) {
|
||||||
defaultPaint(context, offset + paintOffset);
|
defaultPaint(context, offset + paintOffset);
|
||||||
|
_overlayPainter?.paint(context, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
@ -445,15 +445,18 @@ class Block extends StatelessComponent {
|
|||||||
|
|
||||||
abstract class ScrollableListPainter extends Painter {
|
abstract class ScrollableListPainter extends Painter {
|
||||||
void attach(RenderObject renderObject) {
|
void attach(RenderObject renderObject) {
|
||||||
assert(renderObject is RenderBlockViewport);
|
assert(renderObject is RenderBox);
|
||||||
|
assert(renderObject is RenderScrollable);
|
||||||
super.attach(renderObject);
|
super.attach(renderObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderBlockViewport get renderer => renderObject;
|
RenderBox get renderObject => super.renderObject;
|
||||||
|
|
||||||
bool get isVertical => renderer.isVertical;
|
ScrollDirection get scrollDirection {
|
||||||
|
return (renderObject as RenderScrollable)?.scrollDirection;
|
||||||
|
}
|
||||||
|
|
||||||
Size get viewportSize => renderer.size;
|
Size get viewportSize => renderObject.size;
|
||||||
|
|
||||||
double get contentExtent => _contentExtent;
|
double get contentExtent => _contentExtent;
|
||||||
double _contentExtent = 0.0;
|
double _contentExtent = 0.0;
|
||||||
@ -463,7 +466,7 @@ abstract class ScrollableListPainter extends Painter {
|
|||||||
if (_contentExtent == value)
|
if (_contentExtent == value)
|
||||||
return;
|
return;
|
||||||
_contentExtent = value;
|
_contentExtent = value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
double get scrollOffset => _scrollOffset;
|
double get scrollOffset => _scrollOffset;
|
||||||
@ -473,7 +476,7 @@ abstract class ScrollableListPainter extends Painter {
|
|||||||
if (_scrollOffset == value)
|
if (_scrollOffset == value)
|
||||||
return;
|
return;
|
||||||
_scrollOffset = value;
|
_scrollOffset = value;
|
||||||
renderer?.markNeedsPaint();
|
renderObject?.markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a scroll starts. Subclasses may override this method to
|
/// Called when a scroll starts. Subclasses may override this method to
|
||||||
@ -675,7 +678,7 @@ class ScrollableList<T> extends ScrollableWidgetList {
|
|||||||
double snapAlignmentOffset: 0.0,
|
double snapAlignmentOffset: 0.0,
|
||||||
this.items,
|
this.items,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
itemsWrap: false,
|
bool itemsWrap: false,
|
||||||
double itemExtent,
|
double itemExtent,
|
||||||
EdgeDims padding,
|
EdgeDims padding,
|
||||||
ScrollableListPainter scrollableListPainter
|
ScrollableListPainter scrollableListPainter
|
||||||
|
@ -79,6 +79,9 @@ class GridViewport extends VirtualViewport {
|
|||||||
final ExtentsChangedCallback onExtentsChanged;
|
final ExtentsChangedCallback onExtentsChanged;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
|
// TODO(abarth): Support horizontal scrolling;
|
||||||
|
ScrollDirection get scrollDirection => ScrollDirection.vertical;
|
||||||
|
|
||||||
RenderGrid createRenderObject() => new RenderGrid(delegate: delegate);
|
RenderGrid createRenderObject() => new RenderGrid(delegate: delegate);
|
||||||
|
|
||||||
_GridViewportElement createElement() => new _GridViewportElement(this);
|
_GridViewportElement createElement() => new _GridViewportElement(this);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
import 'virtual_viewport.dart';
|
import 'virtual_viewport.dart';
|
||||||
@ -13,22 +15,30 @@ class ScrollableList2 extends Scrollable {
|
|||||||
ScrollableList2({
|
ScrollableList2({
|
||||||
Key key,
|
Key key,
|
||||||
double initialScrollOffset,
|
double initialScrollOffset,
|
||||||
|
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||||
ScrollListener onScroll,
|
ScrollListener onScroll,
|
||||||
SnapOffsetCallback snapOffsetCallback,
|
SnapOffsetCallback snapOffsetCallback,
|
||||||
double snapAlignmentOffset: 0.0,
|
double snapAlignmentOffset: 0.0,
|
||||||
this.itemExtent,
|
this.itemExtent,
|
||||||
|
this.itemsWrap: false,
|
||||||
|
this.padding,
|
||||||
|
this.scrollableListPainter,
|
||||||
this.children
|
this.children
|
||||||
}) : super(
|
}) : super(
|
||||||
key: key,
|
key: key,
|
||||||
initialScrollOffset: initialScrollOffset,
|
initialScrollOffset: initialScrollOffset,
|
||||||
// TODO(abarth): Support horizontal offsets.
|
scrollDirection: scrollDirection,
|
||||||
scrollDirection: ScrollDirection.vertical,
|
|
||||||
onScroll: onScroll,
|
onScroll: onScroll,
|
||||||
snapOffsetCallback: snapOffsetCallback,
|
snapOffsetCallback: snapOffsetCallback,
|
||||||
snapAlignmentOffset: snapAlignmentOffset
|
snapAlignmentOffset: snapAlignmentOffset
|
||||||
);
|
) {
|
||||||
|
assert(itemExtent != null);
|
||||||
|
}
|
||||||
|
|
||||||
final double itemExtent;
|
final double itemExtent;
|
||||||
|
final bool itemsWrap;
|
||||||
|
final EdgeDims padding;
|
||||||
|
final ScrollableListPainter scrollableListPainter;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
ScrollableState createState() => new _ScrollableList2State();
|
ScrollableState createState() => new _ScrollableList2State();
|
||||||
@ -39,20 +49,40 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
|
|||||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||||
|
|
||||||
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
||||||
|
config.scrollableListPainter?.contentExtent = contentExtent;
|
||||||
setState(() {
|
setState(() {
|
||||||
scrollTo(scrollBehavior.updateExtents(
|
scrollTo(scrollBehavior.updateExtents(
|
||||||
contentExtent: contentExtent,
|
contentExtent: config.itemsWrap ? double.INFINITY : contentExtent,
|
||||||
containerExtent: containerExtent,
|
containerExtent: containerExtent,
|
||||||
scrollOffset: scrollOffset
|
scrollOffset: scrollOffset
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dispatchOnScrollStart() {
|
||||||
|
super.dispatchOnScrollStart();
|
||||||
|
config.scrollableListPainter?.scrollStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatchOnScroll() {
|
||||||
|
super.dispatchOnScroll();
|
||||||
|
config.scrollableListPainter?.scrollOffset = scrollOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatchOnScrollEnd() {
|
||||||
|
super.dispatchOnScrollEnd();
|
||||||
|
config.scrollableListPainter?.scrollEnded();
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildContent(BuildContext context) {
|
Widget buildContent(BuildContext context) {
|
||||||
return new ListViewport(
|
return new ListViewport(
|
||||||
startOffset: scrollOffset,
|
|
||||||
itemExtent: config.itemExtent,
|
|
||||||
onExtentsChanged: _handleExtentsChanged,
|
onExtentsChanged: _handleExtentsChanged,
|
||||||
|
startOffset: scrollOffset,
|
||||||
|
scrollDirection: config.scrollDirection,
|
||||||
|
itemExtent: config.itemExtent,
|
||||||
|
itemsWrap: config.itemsWrap,
|
||||||
|
padding: config.padding,
|
||||||
|
overlayPainter: config.scrollableListPainter,
|
||||||
children: config.children
|
children: config.children
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -61,15 +91,26 @@ class _ScrollableList2State extends ScrollableState<ScrollableList2> {
|
|||||||
class ListViewport extends VirtualViewport {
|
class ListViewport extends VirtualViewport {
|
||||||
ListViewport({
|
ListViewport({
|
||||||
Key key,
|
Key key,
|
||||||
this.startOffset,
|
|
||||||
this.itemExtent,
|
|
||||||
this.onExtentsChanged,
|
this.onExtentsChanged,
|
||||||
|
this.startOffset: 0.0,
|
||||||
|
this.scrollDirection: ScrollDirection.vertical,
|
||||||
|
this.itemExtent,
|
||||||
|
this.itemsWrap: false,
|
||||||
|
this.padding,
|
||||||
|
this.overlayPainter,
|
||||||
this.children
|
this.children
|
||||||
});
|
}) {
|
||||||
|
assert(scrollDirection != null);
|
||||||
|
assert(itemExtent != null);
|
||||||
|
}
|
||||||
|
|
||||||
final double startOffset;
|
|
||||||
final double itemExtent;
|
|
||||||
final ExtentsChangedCallback onExtentsChanged;
|
final ExtentsChangedCallback onExtentsChanged;
|
||||||
|
final double startOffset;
|
||||||
|
final ScrollDirection scrollDirection;
|
||||||
|
final double itemExtent;
|
||||||
|
final bool itemsWrap;
|
||||||
|
final EdgeDims padding;
|
||||||
|
final Painter overlayPainter;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);
|
||||||
@ -95,21 +136,39 @@ class _ListViewportElement extends VirtualViewportElement<ListViewport> {
|
|||||||
double _repaintOffsetLimit;
|
double _repaintOffsetLimit;
|
||||||
|
|
||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
|
renderObject.scrollDirection = widget.scrollDirection;
|
||||||
renderObject.itemExtent = widget.itemExtent;
|
renderObject.itemExtent = widget.itemExtent;
|
||||||
|
renderObject.padding = widget.padding;
|
||||||
|
renderObject.overlayPainter = widget.overlayPainter;
|
||||||
super.updateRenderObject();
|
super.updateRenderObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
double _contentExtent;
|
double _contentExtent;
|
||||||
double _containerExtent;
|
double _containerExtent;
|
||||||
|
|
||||||
|
double _getContainerExtentFromRenderObject() {
|
||||||
|
switch (widget.scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return renderObject.size.height;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return renderObject.size.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void layout(BoxConstraints constraints) {
|
void layout(BoxConstraints constraints) {
|
||||||
double contentExtent = widget.itemExtent * widget.children.length;
|
double contentExtent = widget.itemExtent * widget.children.length;
|
||||||
double containerExtent = renderObject.size.height;
|
double containerExtent = _getContainerExtentFromRenderObject();
|
||||||
|
|
||||||
|
_materializedChildBase = math.max(0, widget.startOffset ~/ widget.itemExtent);
|
||||||
|
int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / widget.itemExtent).ceil());
|
||||||
|
|
||||||
|
if (!widget.itemsWrap) {
|
||||||
|
int length = widget.children.length;
|
||||||
|
_materializedChildBase = math.min(length, _materializedChildBase);
|
||||||
|
materializedChildLimit = math.min(length, materializedChildLimit);
|
||||||
|
}
|
||||||
|
|
||||||
_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;
|
_materializedChildCount = materializedChildLimit - _materializedChildBase;
|
||||||
|
|
||||||
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
_repaintOffsetBase = _materializedChildBase * widget.itemExtent;
|
||||||
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
|
_repaintOffsetLimit = materializedChildLimit * widget.itemExtent;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
|
|||||||
|
|
||||||
abstract class VirtualViewport extends RenderObjectWidget {
|
abstract class VirtualViewport extends RenderObjectWidget {
|
||||||
double get startOffset;
|
double get startOffset;
|
||||||
|
ScrollDirection get scrollDirection;
|
||||||
List<Widget> get children;
|
List<Widget> get children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +53,23 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updatePaintOffset() {
|
void _updatePaintOffset() {
|
||||||
renderObject.paintOffset =
|
switch (widget.scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
renderObject.paintOffset = new Offset(0.0, -(widget.startOffset - repaintOffsetBase));
|
||||||
|
break;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
renderObject.paintOffset = new Offset(-(widget.startOffset - repaintOffsetBase), 0.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double get _containerExtent {
|
||||||
|
switch (widget.scrollDirection) {
|
||||||
|
case ScrollDirection.vertical:
|
||||||
|
return renderObject.size.height;
|
||||||
|
case ScrollDirection.horizontal:
|
||||||
|
return renderObject.size.width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRenderObject() {
|
void updateRenderObject() {
|
||||||
@ -67,7 +83,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
if (!renderObject.needsLayout) {
|
if (!renderObject.needsLayout) {
|
||||||
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
if (repaintOffsetBase != null && widget.startOffset < repaintOffsetBase)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
else if (repaintOffsetLimit != null && widget.startOffset + renderObject.size.height > repaintOffsetLimit)
|
else if (repaintOffsetLimit != null && widget.startOffset + _containerExtent > repaintOffsetLimit)
|
||||||
renderObject.markNeedsLayout();
|
renderObject.markNeedsLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +104,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
|
|||||||
List<Widget> newWidgets = new List<Widget>(count);
|
List<Widget> newWidgets = new List<Widget>(count);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
int childIndex = base + i;
|
int childIndex = base + i;
|
||||||
Widget child = widget.children[childIndex];
|
Widget child = widget.children[childIndex % widget.children.length];
|
||||||
Key key = new ValueKey(child.key ?? childIndex);
|
Key key = new ValueKey(child.key ?? childIndex);
|
||||||
newWidgets[i] = new RepaintBoundary(key: key, child: child);
|
newWidgets[i] = new RepaintBoundary(key: key, child: child);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user