
This patch changes the timing of the unmount call until after the component is removed (and marked as such) and allows setState to be called after this point. If this happens, setState will still invoke the closer to do any neccesary cleanup, but doesn't schedule the component for render R=eseidel@chromium.org, eseidel BUG= Review URL: https://codereview.chromium.org/985853002
128 lines
2.9 KiB
Dart
128 lines
2.9 KiB
Dart
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,
|
|
children: [
|
|
new Container(
|
|
style: _scrollAreaStyle,
|
|
inlineStyle: transformStyle,
|
|
children: items
|
|
)
|
|
]
|
|
)
|
|
..events.listen('gestureflingstart', _handleFlingStart)
|
|
..events.listen('gestureflingcancel', _handleFlingCancel)
|
|
..events.listen('gesturescrollupdate', _handleScrollUpdate)
|
|
..events.listen('wheel', _handleWheel);
|
|
}
|
|
|
|
void didUnmount() {
|
|
_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.GestureEvent event) {
|
|
_scrollBy(-event.dy);
|
|
}
|
|
|
|
void _handleFlingStart(sky.GestureEvent event) {
|
|
setState(() {
|
|
_flingCurve = new FlingCurve(-event.velocityY, event.timeStamp);
|
|
_scheduleFlingUpdate();
|
|
});
|
|
}
|
|
|
|
void _handleFlingCancel(sky.GestureEvent event) {
|
|
_stopFling();
|
|
}
|
|
|
|
void _handleWheel(sky.WheelEvent event) {
|
|
_scrollBy(-event.offsetY);
|
|
}
|
|
}
|