Index: sky/examples/fn/widgets/fixedheightscrollable.dart |
diff --git a/sky/examples/fn/widgets/fixedheightscrollable.dart b/sky/examples/fn/widgets/fixedheightscrollable.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ef343e26768f611990c85003263c59732bb5f6f6 |
--- /dev/null |
+++ b/sky/examples/fn/widgets/fixedheightscrollable.dart |
@@ -0,0 +1,127 @@ |
+part of widgets; |
+ |
+abstract class FixedHeightScrollable extends Component { |
+ |
+ static Style _style = new Style(''' |
+ overflow: hidden; |
+ position: relative; |
+ will-change: transform;''' |
+ ); |
+ |
+ static Style _scrollAreaStyle = new Style(''' |
+ position:relative; |
+ will-change: transform;''' |
+ ); |
+ |
+ double itemHeight; |
+ double height; |
+ double minOffset; |
+ double maxOffset; |
+ |
+ double _scrollOffset = 0.0; |
+ FlingCurve _flingCurve; |
+ int _flingAnimationId; |
+ |
+ FixedHeightScrollable({ |
+ Object key, |
+ this.itemHeight, |
+ this.height, |
+ this.minOffset, |
+ this.maxOffset |
+ }) : super(key: key) {} |
+ |
+ |
+ List<Node> renderItems(int start, int count); |
+ |
+ Node render() { |
+ int drawCount = (height / itemHeight).round() + 1; |
+ double alignmentDelta = -_scrollOffset % itemHeight; |
+ if (alignmentDelta != 0.0) { |
+ alignmentDelta -= itemHeight; |
+ } |
+ |
+ double drawStart = _scrollOffset + alignmentDelta; |
+ int itemNumber = (drawStart / itemHeight).floor(); |
+ |
+ var transformStyle = |
+ 'transform: translateY(${(alignmentDelta).toStringAsFixed(2)}px)'; |
+ |
+ var items = renderItems(itemNumber, drawCount); |
+ |
+ return new Container( |
+ style: _style, |
+ onFlingStart: _handleFlingStart, |
+ onFlingCancel: _handleFlingCancel, |
+ onScrollUpdate: _handleScrollUpdate, |
+ onWheel: _handleWheel, |
+ children: [ |
+ new Container( |
+ style: _scrollAreaStyle, |
+ inlineStyle: transformStyle, |
+ children: items |
+ ) |
+ ] |
+ ); |
+ } |
+ |
+ void willUnmount() { |
+ _stopFling(); |
+ } |
+ |
+ bool _scrollBy(double scrollDelta) { |
+ var newScrollOffset = _scrollOffset + scrollDelta; |
+ if (minOffset != null && newScrollOffset < minOffset) { |
+ newScrollOffset = minOffset; |
+ } else if (maxOffset != null && newScrollOffset > maxOffset) { |
+ newScrollOffset = maxOffset; |
+ } |
+ if (newScrollOffset == _scrollOffset) { |
+ return false; |
+ } |
+ |
+ setState(() { |
+ _scrollOffset = newScrollOffset; |
+ }); |
+ return true; |
+ } |
+ |
+ void _scheduleFlingUpdate() { |
+ _flingAnimationId = sky.window.requestAnimationFrame(_updateFling); |
+ } |
+ |
+ void _stopFling() { |
+ if (_flingAnimationId == null) { |
+ return; |
+ } |
+ |
+ sky.window.cancelAnimationFrame(_flingAnimationId); |
+ _flingCurve = null; |
+ _flingAnimationId = null; |
+ } |
+ |
+ void _updateFling(double timeStamp) { |
+ double scrollDelta = _flingCurve.update(timeStamp); |
+ if (!_scrollBy(scrollDelta)) |
+ return _stopFling(); |
+ _scheduleFlingUpdate(); |
+ } |
+ |
+ void _handleScrollUpdate(sky.Event event) { |
+ _scrollBy(-event.dy); |
+ } |
+ |
+ void _handleFlingStart(sky.Event event) { |
+ setState(() { |
+ _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); |
+ _scheduleFlingUpdate(); |
+ }); |
+ } |
+ |
+ void _handleFlingCancel(sky.Event event) { |
+ _stopFling(); |
+ } |
+ |
+ void _handleWheel(sky.Event event) { |
+ _scrollBy(-event.offsetY); |
+ } |
+} |