diff --git a/sky/sdk/example/widgets/block_viewport.dart b/sky/sdk/example/widgets/block_viewport.dart index 6db32ff3370fa..aa7e53ab8f077 100644 --- a/sky/sdk/example/widgets/block_viewport.dart +++ b/sky/sdk/example/widgets/block_viewport.dart @@ -15,6 +15,7 @@ import 'package:sky/widgets/widget.dart'; class BlockViewportApp extends App { + BlockViewportLayoutState layoutState = new BlockViewportLayoutState(); List lengths = []; double offset = 0.0; @@ -96,7 +97,8 @@ class BlockViewportApp extends App { child: new BlockViewport( builder: builder, startOffset: offset, - token: lengths.length + token: lengths.length, + layoutState: layoutState ) ) ), diff --git a/sky/sdk/example/widgets/card_collection.dart b/sky/sdk/example/widgets/card_collection.dart index a53369d68aa9a..6725e372e1a58 100644 --- a/sky/sdk/example/widgets/card_collection.dart +++ b/sky/sdk/example/widgets/card_collection.dart @@ -2,62 +2,145 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/curves.dart'; import 'package:sky/base/lerp.dart'; import 'package:sky/painting/text_style.dart'; import 'package:sky/theme/colors.dart'; +import 'package:sky/widgets/animated_component.dart'; import 'package:sky/widgets/basic.dart'; +import 'package:sky/widgets/block_viewport.dart'; import 'package:sky/widgets/card.dart'; import 'package:sky/widgets/dismissable.dart'; -import 'package:sky/widgets/scaffold.dart'; import 'package:sky/widgets/variable_height_scrollable.dart'; +import 'package:sky/widgets/scaffold.dart'; import 'package:sky/widgets/theme.dart'; import 'package:sky/widgets/tool_bar.dart'; import 'package:sky/widgets/widget.dart'; -import 'package:sky/theme/colors.dart' as colors; import 'package:sky/widgets/task_description.dart'; +class CardModel { + CardModel(this.value, this.height, this.color); + int value; + double height; + Color color; + AnimationPerformance performance; + String get label => "Item $value"; + String get key => value.toString(); + bool operator ==(other) => other is CardModel && other.value == value; + int get hashCode => 373 * 37 * value.hashCode; +} + +class ShrinkingCard extends AnimatedComponent { + + ShrinkingCard({ + String key, + CardModel this.card, + Function this.onUpdated, + Function this.onCompleted + }) : super(key: key); + + CardModel card; + Function onUpdated; + Function onCompleted; + + double get currentHeight => card.performance.variable.value; + + void initState() { + assert(card.performance != null); + card.performance.addListener(handleAnimationProgress); + watch(card.performance); + } + + void handleAnimationProgress() { + if (card.performance.isCompleted) { + if (onCompleted != null) + onCompleted(); + } else if (onUpdated != null) { + onUpdated(); + } + } + + void syncFields(ShrinkingCard source) { + card = source.card; + onCompleted = source.onCompleted; + onUpdated = source.onUpdated; + super.syncFields(source); + } + + Widget build() => new Container(height: currentHeight); +} class CardCollectionApp extends App { final TextStyle cardLabelStyle = new TextStyle(color: white, fontSize: 18.0, fontWeight: bold); - final List cardHeights = [ - 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, - 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, - 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, - 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0 - ]; - - List visibleCardIndices; + BlockViewportLayoutState layoutState = new BlockViewportLayoutState(); + List cardModels; void initState() { - visibleCardIndices = new List.generate(cardHeights.length, (i) => i); + List cardHeights = [ + 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, + 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, + 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0 + ]; + cardModels = new List.generate(cardHeights.length, (i) { + Color color = lerpColor(Red[300], Blue[900], i / cardHeights.length); + return new CardModel(i, cardHeights[i], color); + }); super.initState(); } - void dismissCard(int cardIndex) { + void shrinkCard(CardModel card, int index) { + if (card.performance != null) + return; + layoutState.invalidate([index]); setState(() { - visibleCardIndices.remove(cardIndex); + assert(card.performance == null); + card.performance = new AnimationPerformance() + ..duration = const Duration(milliseconds: 300) + ..variable = new AnimatedType( + card.height + kCardMargins.top + kCardMargins.bottom, + end: 0.0, + curve: ease, + interval: new Interval(0.5, 1.0) + ) + ..play(); }); } - Widget _builder(int index) { - if (index >= visibleCardIndices.length) + void dismissCard(CardModel card) { + if (cardModels.contains(card)) { + setState(() { + cardModels.remove(card); + }); + } + } + + Widget builder(int index) { + if (index >= cardModels.length) return null; + CardModel card = cardModels[index]; + + if (card.performance != null) { + return new ShrinkingCard( + key: card.key, + card: card, + onUpdated: () { layoutState.invalidate([index]); }, + onCompleted: () { dismissCard(card); } + ); + } - int cardIndex = visibleCardIndices[index]; - Color color = lerpColor(Red[500], Blue[500], cardIndex / cardHeights.length); - Widget label = new Text("Item ${cardIndex}", style: cardLabelStyle); return new Dismissable( - key: cardIndex.toString(), - onDismissed: () { dismissCard(cardIndex); }, + key: card.key, + onDismissed: () { shrinkCard(card, index); }, child: new Card( - color: color, + color: card.color, child: new Container( - height: cardHeights[cardIndex], + height: card.height, padding: const EdgeDims.all(8.0), - child: new Center(child: label) + child: new Center(child: new Text(card.label, style: cardLabelStyle)) ) ) ); @@ -68,16 +151,17 @@ class CardCollectionApp extends App { padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]), child: new VariableHeightScrollable( - builder: _builder, - token: visibleCardIndices.length + builder: builder, + token: cardModels.length, + layoutState: layoutState ) ); return new Theme( data: new ThemeData( brightness: ThemeBrightness.light, - primarySwatch: colors.Blue, - accentColor: colors.RedAccent[200] + primarySwatch: Blue, + accentColor: RedAccent[200] ), child: new TaskDescription( label: 'Cards', diff --git a/sky/sdk/lib/widgets/block_viewport.dart b/sky/sdk/lib/widgets/block_viewport.dart index 65380a5d2c4e0..da60075b4bbfc 100644 --- a/sky/sdk/lib/widgets/block_viewport.dart +++ b/sky/sdk/lib/widgets/block_viewport.dart @@ -12,13 +12,6 @@ import 'package:sky/widgets/widget.dart'; // return null if index is greater than index of last entry typedef Widget IndexedBuilder(int index); -typedef void LayoutChangedCallback( - int firstVisibleChildIndex, - int visibleChildCount, - UnmodifiableListView childOffsets, - bool didReachLastChild -); - class _Key { const _Key(this.type, this.key); factory _Key.fromWidget(Widget widget) => new _Key(widget.runtimeType, widget.key); @@ -26,24 +19,80 @@ class _Key { final String key; bool operator ==(other) => other is _Key && other.type == type && other.key == key; int get hashCode => 373 * 37 * type.hashCode + key.hashCode; + String toString() => "_Key(type: $type, key: $key)"; +} + +typedef void LayoutChangedCallback(); + +class BlockViewportLayoutState { + BlockViewportLayoutState() + : _childOffsets = [0.0], + _firstVisibleChildIndex = 0, + _visibleChildCount = 0, + _didReachLastChild = false + { + _readOnlyChildOffsets = new UnmodifiableListView(_childOffsets); + } + + Map<_Key, Widget> _childrenByKey = new Map<_Key, Widget>(); + bool _dirty = true; + + int _firstVisibleChildIndex; + int get firstVisibleChildIndex => _firstVisibleChildIndex; + + int _visibleChildCount; + int get visibleChildCount => _visibleChildCount; + + // childOffsets contains the offsets of each child from the top of the + // list up to the last one we've ever created, and the offset of the + // end of the last one. If there are no children, then the only offset + // is 0.0. + List _childOffsets; + UnmodifiableListView _readOnlyChildOffsets; + UnmodifiableListView get childOffsets => _readOnlyChildOffsets; + double get contentsSize => _childOffsets.last; + + bool _didReachLastChild; + bool get didReachLastChild => _didReachLastChild; + + Set _invalidIndices = new Set(); + bool get isValid => _invalidIndices.length == 0; + // Notify the BlockViewport that the children at indices have either + // changed size and/or changed type. + void invalidate(Iterable indices) { + _invalidIndices.addAll(indices); + } + + final List _listeners = new List(); + void addListener(Function listener) { + _listeners.add(listener); + } + void removeListener(Function listener) { + _listeners.remove(listener); + } + void _notifyListeners() { + List localListeners = new List.from(_listeners); + for (Function listener in localListeners) + listener(); + } } class BlockViewport extends RenderObjectWrapper { - BlockViewport({ this.builder, this.startOffset, this.token, this.onLayoutChanged, String key }) - : super(key: key); + BlockViewport({ this.builder, this.startOffset, this.token, this.layoutState, String key }) + : super(key: key) { + assert(this.layoutState != null); + } IndexedBuilder builder; double startOffset; Object token; - LayoutChangedCallback onLayoutChanged; + BlockViewportLayoutState layoutState; RenderBlockViewport get root => super.root; RenderBlockViewport createNode() => new RenderBlockViewport(); - Map<_Key, Widget> _childrenByKey = new Map<_Key, Widget>(); - void walkChildren(WidgetTreeWalker walker) { - for (Widget child in _childrenByKey.values) + for (Widget child in layoutState._childrenByKey.values) walker(child); } @@ -69,7 +118,7 @@ class BlockViewport extends RenderObjectWrapper { } void remove() { - for (Widget child in _childrenByKey.values) { + for (Widget child in layoutState._childrenByKey.values) { assert(child != null); removeChild(child); } @@ -86,23 +135,15 @@ class BlockViewport extends RenderObjectWrapper { super.didUnmount(); } - // _offsets contains the offsets of each child from the top of the - // list up to the last one we've ever created, and the offset of the - // end of the last one. If there's no children, then the only offset - // is 0.0. - List _offsets = [0.0]; - int _currentStartIndex = 0; - int _currentChildCount = 0; - bool _didReachLastChild = false; - int _findIndexForOffsetBeforeOrAt(double offset) { + final List offsets = layoutState._childOffsets; int left = 0; - int right = _offsets.length - 1; + int right = offsets.length - 1; while (right >= left) { int middle = left + ((right - left) ~/ 2); - if (_offsets[middle] < offset) { + if (offsets[middle] < offset) { left = middle + 1; - } else if (_offsets[middle] > offset) { + } else if (offsets[middle] > offset) { right = middle - 1; } else { return middle; @@ -111,94 +152,154 @@ class BlockViewport extends RenderObjectWrapper { return right; } - bool _dirty = true; - bool retainStatefulNodeIfPossible(BlockViewport newNode) { + assert(layoutState == newNode.layoutState); retainStatefulRenderObjectWrapper(newNode); if (startOffset != newNode.startOffset) { - _dirty = true; + layoutState._dirty = true; startOffset = newNode.startOffset; } if (token != newNode.token || builder != newNode.builder) { - _dirty = true; + layoutState._dirty = true; builder = newNode.builder; token = newNode.token; - _offsets = [0.0]; - _didReachLastChild = false; + layoutState._didReachLastChild = false; + layoutState._childOffsets = [0.0]; + layoutState._invalidIndices = new Set(); } return true; } void syncRenderObject(BlockViewport old) { super.syncRenderObject(old); - if (_dirty) { + if (layoutState._dirty || !layoutState.isValid) { root.markNeedsLayout(); } else { - if (_currentChildCount > 0) { - assert(_currentStartIndex >= 0); + if (layoutState._visibleChildCount > 0) { + assert(layoutState.firstVisibleChildIndex >= 0); assert(builder != null); assert(root != null); - int lastIndex = _currentStartIndex + _currentChildCount - 1; - for (int index = _currentStartIndex; index <= lastIndex; index += 1) { + final int startIndex = layoutState._firstVisibleChildIndex; + int lastIndex = startIndex + layoutState._visibleChildCount - 1; + for (int index = startIndex; index <= lastIndex; index += 1) { Widget widget = builder(index); assert(widget != null); assert(widget.key != null); _Key key = new _Key.fromWidget(widget); - Widget oldWidget = _childrenByKey[key]; + Widget oldWidget = layoutState._childrenByKey[key]; assert(oldWidget != null); assert(oldWidget.root.parent == root); widget = syncChild(widget, oldWidget, root.childAfter(oldWidget.root)); assert(widget != null); - _childrenByKey[key] = widget; + layoutState._childrenByKey[key] = widget; } } } } + // Build the widget at index, and use its maxIntrinsicHeight to fix up + // the offsets from index+1 to endIndex. Return the newWidget. + Widget _getWidgetAndRecomputeOffsets(int index, int endIndex, BoxConstraints innerConstraints) { + final List offsets = layoutState._childOffsets; + // Create the newWidget at index. + assert(index >= 0); + assert(endIndex > index); + assert(endIndex < offsets.length); + assert(builder != null); + Widget newWidget = builder(index); + assert(newWidget != null); + assert(newWidget.key != null); + final _Key key = new _Key.fromWidget(newWidget); + Widget oldWidget = layoutState._childrenByKey[key]; + newWidget = syncChild(newWidget, oldWidget, _omit); + assert(newWidget != null); + // Update the offsets based on the newWidget's height. + RenderBox widgetRoot = newWidget.root; + assert(widgetRoot is RenderBox); + double newHeight = widgetRoot.getMaxIntrinsicHeight(innerConstraints); + double oldHeight = offsets[index + 1] - offsets[index]; + double heightDelta = newHeight - oldHeight; + for (int i = index + 1; i <= endIndex; i++) + offsets[i] += heightDelta; + return newWidget; + } + Widget _getWidget(int index, BoxConstraints innerConstraints) { + final List offsets = layoutState._childOffsets; + assert(index >= 0); + Widget widget = builder == null ? null : builder(index); + if (widget == null) + return null; + assert(widget.key != null); // items in lists must have keys + final _Key key = new _Key.fromWidget(widget); + Widget oldWidget = layoutState._childrenByKey[key]; + widget = syncChild(widget, oldWidget, _omit); + if (index >= offsets.length - 1) { + assert(index == offsets.length - 1); + final double widgetStartOffset = offsets[index]; + RenderBox widgetRoot = widget.root; + assert(widgetRoot is RenderBox); + final double widgetEndOffset = widgetStartOffset + widgetRoot.getMaxIntrinsicHeight(innerConstraints); + offsets.add(widgetEndOffset); + } + return widget; + } + + void layout(BoxConstraints constraints) { + if (!layoutState._dirty && layoutState.isValid) + return; + layoutState._dirty = false; + LayoutCallbackBuilderHandle handle = enterLayoutCallbackBuilder(); try { - assert(index >= 0); - Widget widget = builder == null ? null : builder(index); - if (widget == null) - return null; - assert(widget.key != null); // items in lists must have keys - final _Key key = new _Key.fromWidget(widget); - Widget oldWidget = _childrenByKey[key]; - widget = syncChild(widget, oldWidget, _omit); - if (oldWidget != null) - _childrenByKey[key] = widget; - if (index >= _offsets.length - 1) { - assert(index == _offsets.length - 1); - final double widgetStartOffset = _offsets[index]; - RenderBox widgetRoot = widget.root; - assert(widgetRoot is RenderBox); - final double widgetEndOffset = widgetStartOffset + widgetRoot.getMaxIntrinsicHeight(innerConstraints); - _offsets.add(widgetEndOffset); - } - return widget; + _doLayout(constraints); } finally { exitLayoutCallbackBuilder(handle); } - } - void layout(BoxConstraints constraints) { - if (!_dirty) - return; - _dirty = false; + layoutState._notifyListeners(); + } + void _doLayout(BoxConstraints constraints) { Map<_Key, Widget> newChildren = new Map<_Key, Widget>(); Map builtChildren = new Map(); + final List offsets = layoutState._childOffsets; + final Map<_Key, Widget> childrenByKey = layoutState._childrenByKey; final double height = root.size.height; final double endOffset = startOffset + height; BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.constrainWidth()); + // Before doing the actual layout, fix the offsets for the widgets + // whose size or type has changed. + if (!layoutState.isValid && offsets.length > 0) { + List invalidIndices = layoutState._invalidIndices.toList(); + invalidIndices.sort(); + // Ensure all of the offsets after invalidIndices[0] are updated. + if (invalidIndices.last < offsets.length - 1) + invalidIndices.add(offsets.length - 1); + for (int i = 0; i < invalidIndices.length - 1; i += 1) { + int index = invalidIndices[i]; + int endIndex = invalidIndices[i + 1]; + Widget widget = _getWidgetAndRecomputeOffsets(index, endIndex, innerConstraints); + _Key widgetKey = new _Key.fromWidget(widget); + bool isVisible = offsets[index] < endOffset && offsets[index + 1] >= startOffset; + if (isVisible) { + newChildren[widgetKey] = widget; + builtChildren[index] = widget; + } else { + childrenByKey.remove(widgetKey); + syncChild(null, widget, null); + } + } + } + layoutState._invalidIndices.clear(); + int startIndex; bool haveChildren; if (startOffset <= 0.0) { startIndex = 0; - if (_offsets.length > 1) { + if (offsets.length > 1) { haveChildren = true; } else { Widget widget = _getWidget(startIndex, innerConstraints); @@ -208,41 +309,41 @@ class BlockViewport extends RenderObjectWrapper { haveChildren = true; } else { haveChildren = false; - _didReachLastChild = true; + layoutState._didReachLastChild = true; } } } else { startIndex = _findIndexForOffsetBeforeOrAt(startOffset); - if (startIndex == _offsets.length - 1) { + if (startIndex == offsets.length - 1) { // We don't have an offset on the list that is beyond the start offset. - assert(_offsets.last <= startOffset); + assert(offsets.last <= startOffset); // Fill the list until this isn't true or until we know that the // list is complete (and thus we are overscrolled). while (true) { Widget widget = _getWidget(startIndex, innerConstraints); if (widget == null) { - _didReachLastChild = true; + layoutState._didReachLastChild = true; break; } _Key widgetKey = new _Key.fromWidget(widget); - if (_offsets.last > startOffset) { + if (offsets.last > startOffset) { newChildren[widgetKey] = widget; builtChildren[startIndex] = widget; break; } - if (!_childrenByKey.containsKey(widgetKey)) { + if (!childrenByKey.containsKey(widgetKey)) { // we don't actually need this one, release it syncChild(null, widget, null); } // else we'll get rid of it later, when we remove old children startIndex += 1; - assert(startIndex == _offsets.length - 1); + assert(startIndex == offsets.length - 1); } - if (_offsets.last > startOffset) { + if (offsets.last > startOffset) { // If we're here, we have at least one child, so our list has // at least two offsets, the top of the child and the bottom // of the child. - assert(_offsets.length >= 2); - assert(startIndex == _offsets.length - 2); + assert(offsets.length >= 2); + assert(startIndex == offsets.length - 2); haveChildren = true; } else { // If we're here, there are no children to show. @@ -253,20 +354,20 @@ class BlockViewport extends RenderObjectWrapper { } } assert(haveChildren != null); - assert(haveChildren || _didReachLastChild); + assert(haveChildren || layoutState._didReachLastChild); assert(startIndex >= 0); - assert(startIndex < _offsets.length); + assert(startIndex < offsets.length); int index = startIndex; if (haveChildren) { // Build all the widgets we need. - root.startOffset = _offsets[index] - startOffset; - while (_offsets[index] < endOffset) { + root.startOffset = offsets[index] - startOffset; + while (offsets[index] < endOffset) { if (!builtChildren.containsKey(index)) { Widget widget = _getWidget(index, innerConstraints); if (widget == null) { - _didReachLastChild = true; + layoutState._didReachLastChild = true; break; } newChildren[new _Key.fromWidget(widget)] = widget; @@ -278,9 +379,9 @@ class BlockViewport extends RenderObjectWrapper { } // Remove any old children. - for (_Key oldChildKey in _childrenByKey.keys) { + for (_Key oldChildKey in childrenByKey.keys) { if (!newChildren.containsKey(oldChildKey)) - syncChild(null, _childrenByKey[oldChildKey], null); // calls detachChildRoot() + syncChild(null, childrenByKey[oldChildKey], null); // calls detachChildRoot() } if (haveChildren) { @@ -302,18 +403,9 @@ class BlockViewport extends RenderObjectWrapper { } } - _childrenByKey = newChildren; - _currentStartIndex = startIndex; - _currentChildCount = _childrenByKey.length; - - if (onLayoutChanged != null) { - onLayoutChanged( - _currentStartIndex, - _currentChildCount, - new UnmodifiableListView(_offsets), - _didReachLastChild - ); - } + layoutState._childrenByKey = newChildren; + layoutState._firstVisibleChildIndex = startIndex; + layoutState._visibleChildCount = newChildren.length; } } diff --git a/sky/sdk/lib/widgets/card.dart b/sky/sdk/lib/widgets/card.dart index 4d0e3ab49db9c..40ade1a96923f 100644 --- a/sky/sdk/lib/widgets/card.dart +++ b/sky/sdk/lib/widgets/card.dart @@ -5,6 +5,8 @@ import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/material.dart'; +const EdgeDims kCardMargins = const EdgeDims.all(4.0); + /// A material design card /// /// @@ -16,7 +18,7 @@ class Card extends Component { Widget build() { return new Container( - margin: const EdgeDims.all(4.0), + margin: kCardMargins, child: new Material( color: color, type: MaterialType.card, diff --git a/sky/sdk/lib/widgets/variable_height_scrollable.dart b/sky/sdk/lib/widgets/variable_height_scrollable.dart index 58653aeabfa12..fcca28242c0d4 100644 --- a/sky/sdk/lib/widgets/variable_height_scrollable.dart +++ b/sky/sdk/lib/widgets/variable_height_scrollable.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:collection'; - import 'package:sky/animation/scroll_behavior.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/block_viewport.dart'; @@ -14,18 +12,39 @@ class VariableHeightScrollable extends Scrollable { VariableHeightScrollable({ String key, this.builder, - this.token + this.token, + this.layoutState }) : super(key: key); IndexedBuilder builder; Object token; + BlockViewportLayoutState layoutState; + + // When the token changes the scrollable's contents may have + // changed. Remember as much so that after the new contents + // have been laid out we can adjust the scrollOffset so that + // the last page of content is still visible. bool _contentsChanged = true; + void initState() { + assert(layoutState != null); + layoutState.removeListener(_handleLayoutChanged); + layoutState.addListener(_handleLayoutChanged); + super.initState(); + } + void syncFields(VariableHeightScrollable source) { builder = source.builder; if (token != source.token) _contentsChanged = true; token = source.token; + if (layoutState != source.layoutState) { + // Warning: this is unlikely to be what you intended. + assert(source.layoutState != null); + layoutState == source.layoutState; + layoutState.removeListener(_handleLayoutChanged); + layoutState.addListener(_handleLayoutChanged); + } super.syncFields(source); } @@ -36,15 +55,9 @@ class VariableHeightScrollable extends Scrollable { scrollBehavior.containerSize = newSize.height; } - void _handleLayoutChanged( - int firstVisibleChildIndex, - int visibleChildCount, - UnmodifiableListView childOffsets, - bool didReachLastChild - ) { - assert(childOffsets.length > 0); - if (didReachLastChild) { - scrollBehavior.contentsSize = childOffsets.last; + void _handleLayoutChanged() { + if (layoutState.didReachLastChild) { + scrollBehavior.contentsSize = layoutState.contentsSize; if (_contentsChanged && scrollOffset > scrollBehavior.maxScrollOffset) { _contentsChanged = false; settleScrollOffset(); @@ -59,7 +72,7 @@ class VariableHeightScrollable extends Scrollable { callback: _handleSizeChanged, child: new BlockViewport( builder: builder, - onLayoutChanged: _handleLayoutChanged, + layoutState: layoutState, startOffset: scrollOffset, token: token ) diff --git a/sky/tests/examples/card_collection-expected.txt b/sky/tests/examples/card_collection-expected.txt index c06d573936ef9..a313ac9bdf687 100644 --- a/sky/tests/examples/card_collection-expected.txt +++ b/sky/tests/examples/card_collection-expected.txt @@ -32,7 +32,7 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xfff44336), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffe57373), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 52.0), Paint(color:Color(0xff000000))) @@ -62,10 +62,10 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffee453b), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffdd7174), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 68.0), Paint(color:Color(0xff000000))) +2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 67.0), Paint(color:Color(0xff000000))) 2 | | | | | | | | | | | | clipRRect() 2 | | | | | | | | | | | | paintChild RenderConstrainedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 @@ -73,26 +73,26 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | | | paintChild RenderPositionedBox at Point(12.0, 12.0) 2 | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | paintChild RenderParagraph at Point(359.0, 23.5) +2 | | | | | | | | | | | | | | | paintChild RenderParagraph at Point(359.0, 23.0) 2 | | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | | translate(359.0, 23.5) -2 | | | | | | | | | | | | | | | | translate(-359.0, -23.5) +2 | | | | | | | | | | | | | | | | translate(359.0, 23.0) +2 | | | | | | | | | | | | | | | | translate(-359.0, -23.0) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 196.0) +2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 195.0) 2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 196.0) +2 | | | | | | | paintChild RenderOpacity at Point(8.0, 195.0) 2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 196.0) +2 | | | | | | | | paintChild RenderTransform at Point(8.0, 195.0) 2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 196.0) +2 | | | | | | | | | translate(8.0, 195.0) 2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) 2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffe84740), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd56f76), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 86.0), Paint(color:Color(0xff000000))) @@ -109,23 +109,23 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | | | translate(-359.0, -32.5) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 286.0) +2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 285.0) 2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 286.0) +2 | | | | | | | paintChild RenderOpacity at Point(8.0, 285.0) 2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 286.0) +2 | | | | | | | | paintChild RenderTransform at Point(8.0, 285.0) 2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 286.0) +2 | | | | | | | | | translate(8.0, 285.0) 2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) 2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffe24945), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffcd6e78), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 50.0), Paint(color:Color(0xff000000))) +2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 150.0), Paint(color:Color(0xff000000))) 2 | | | | | | | | | | | | clipRRect() 2 | | | | | | | | | | | | paintChild RenderConstrainedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 @@ -133,26 +133,26 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | | | paintChild RenderPositionedBox at Point(12.0, 12.0) 2 | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | paintChild RenderParagraph at Point(359.0, 14.5) +2 | | | | | | | | | | | | | | | paintChild RenderParagraph at Point(359.0, 64.5) 2 | | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | | translate(359.0, 14.5) -2 | | | | | | | | | | | | | | | | translate(-359.0, -14.5) +2 | | | | | | | | | | | | | | | | translate(359.0, 64.5) +2 | | | | | | | | | | | | | | | | translate(-359.0, -64.5) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 340.0) +2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 439.0) 2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 340.0) +2 | | | | | | | paintChild RenderOpacity at Point(8.0, 439.0) 2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 340.0) +2 | | | | | | | | paintChild RenderTransform at Point(8.0, 439.0) 2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 340.0) +2 | | | | | | | | | translate(8.0, 439.0) 2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) 2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffdc4c4b), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffc56c79), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 64.0), Paint(color:Color(0xff000000))) @@ -169,20 +169,20 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | | | translate(-359.0, -21.5) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 408.0) +2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 507.0) 2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 408.0) +2 | | | | | | | paintChild RenderOpacity at Point(8.0, 507.0) 2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 408.0) +2 | | | | | | | | paintChild RenderTransform at Point(8.0, 507.0) 2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 408.0) +2 | | | | | | | | | translate(8.0, 507.0) 2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) 2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd64e50), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffbd6a7b), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 59.0), Paint(color:Color(0xff000000))) @@ -199,20 +199,20 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | | | translate(-359.0, -19.0) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 471.0) +2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 570.0) 2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 471.0) +2 | | | | | | | paintChild RenderOpacity at Point(8.0, 570.0) 2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 471.0) +2 | | | | | | | | paintChild RenderTransform at Point(8.0, 570.0) 2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 471.0) +2 | | | | | | | | | translate(8.0, 570.0) 2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) 2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) 2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) 2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd05055), drawLooper:true)) +2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffb5697d), drawLooper:true)) 2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) 2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 88.0), Paint(color:Color(0xff000000))) @@ -229,36 +229,6 @@ PAINT FOR FRAME #2 ---------------------------------------------- 2 | | | | | | | | | | | | | | | | translate(-359.0, -33.5) 2 | | | | | | | | | | | | restore 2 | | | | | | | | | restore -2 | | | | | | paintChild RenderSizeObserver at Point(8.0, 563.0) -2 | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | paintChild RenderOpacity at Point(8.0, 563.0) -2 | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | paintChild RenderTransform at Point(8.0, 563.0) -2 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | save -2 | | | | | | | | | translate(8.0, 563.0) -2 | | | | | | | | | concat([1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) -2 | | | | | | | | | paintChild RenderPadding at Point(0.0, 0.0) -2 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | paintChild RenderDecoratedBox at Point(4.0, 4.0) -2 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffca535a), drawLooper:true)) -2 | | | | | | | | | | | paintChild RenderClipRRect at Point(4.0, 4.0) -2 | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | saveLayer(Rect.fromLTRB(4.0, 4.0, 780.0, 100.0), Paint(color:Color(0xff000000))) -2 | | | | | | | | | | | | clipRRect() -2 | | | | | | | | | | | | paintChild RenderConstrainedBox at Point(4.0, 4.0) -2 | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | paintChild RenderPadding at Point(4.0, 4.0) -2 | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | paintChild RenderPositionedBox at Point(12.0, 12.0) -2 | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | paintChild RenderParagraph at Point(359.0, 39.5) -2 | | | | | | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -2 | | | | | | | | | | | | | | | | translate(359.0, 39.5) -2 | | | | | | | | | | | | | | | | translate(-359.0, -39.5) -2 | | | | | | | | | | | | restore -2 | | | | | | | | | restore 2 | | | | | | restore 2 | | paintChild RenderDecoratedBox at Point(0.0, 0.0) 2 | | | TestPaintingCanvas() constructor: 800.0 x 600.0