Cleanup how we manage scrollOffset in Scrollable
- Introduce _setScrollOffset as a backend for the animations so that scrollTo can stop animations. - Create a single function that stops both kinds of scroll animations. - Refactor how we update the bounds for bounded scroll behaviors so that we update the bounds and compute the new scroll offset at the same time.
This commit is contained in:
parent
010589be9e
commit
d3eaff2765
@ -17,32 +17,33 @@ abstract class ScrollBehavior {
|
|||||||
|
|
||||||
class BoundedBehavior extends ScrollBehavior {
|
class BoundedBehavior extends ScrollBehavior {
|
||||||
BoundedBehavior({ double contentsSize: 0.0, double containerSize: 0.0 })
|
BoundedBehavior({ double contentsSize: 0.0, double containerSize: 0.0 })
|
||||||
: _contentsSize = contentsSize,
|
: _contentsExtents = contentsSize,
|
||||||
_containerSize = containerSize;
|
_containerExtents = containerSize;
|
||||||
|
|
||||||
double _contentsSize;
|
double _contentsExtents;
|
||||||
double get contentsSize => _contentsSize;
|
double get contentsExtents => _contentsExtents;
|
||||||
void set contentsSize (double value) {
|
|
||||||
if (_contentsSize != value) {
|
|
||||||
_contentsSize = value;
|
|
||||||
// TODO(ianh) now what? what if we have a simulation ongoing?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _containerSize;
|
double _containerExtents;
|
||||||
double get containerSize => _containerSize;
|
double get containerExtents => _containerExtents;
|
||||||
void set containerSize (double value) {
|
|
||||||
if (_containerSize != value) {
|
/// Returns the new scrollOffset.
|
||||||
_containerSize = value;
|
double updateExtents({
|
||||||
// TODO(ianh) now what? what if we have a simulation ongoing?
|
double contentsExtents,
|
||||||
}
|
double containerExtents,
|
||||||
|
double scrollOffset: 0.0
|
||||||
|
}) {
|
||||||
|
if (contentsExtents != null)
|
||||||
|
_contentsExtents = contentsExtents;
|
||||||
|
if (containerExtents != null)
|
||||||
|
_containerExtents = containerExtents;
|
||||||
|
return scrollOffset.clamp(minScrollOffset, maxScrollOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
final double minScrollOffset = 0.0;
|
final double minScrollOffset = 0.0;
|
||||||
double get maxScrollOffset => math.max(0.0, _contentsSize - _containerSize);
|
double get maxScrollOffset => math.max(0.0, _contentsExtents - _containerExtents);
|
||||||
|
|
||||||
double applyCurve(double scrollOffset, double scrollDelta) {
|
double applyCurve(double scrollOffset, double scrollDelta) {
|
||||||
return (scrollOffset + scrollDelta).clamp(0.0, maxScrollOffset);
|
return (scrollOffset + scrollDelta).clamp(minScrollOffset, maxScrollOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ class OverscrollBehavior extends BoundedBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OverscrollWhenScrollableBehavior extends OverscrollBehavior {
|
class OverscrollWhenScrollableBehavior extends OverscrollBehavior {
|
||||||
bool get isScrollable => contentsSize > containerSize;
|
bool get isScrollable => contentsExtents > containerExtents;
|
||||||
|
|
||||||
Simulation release(double position, double velocity) {
|
Simulation release(double position, double velocity) {
|
||||||
if (isScrollable || position < minScrollOffset || position > maxScrollOffset)
|
if (isScrollable || position < minScrollOffset || position > maxScrollOffset)
|
||||||
|
@ -22,7 +22,6 @@ import 'package:sky/widgets/scrollable.dart';
|
|||||||
|
|
||||||
export 'package:sky/widgets/mixed_viewport.dart' show MixedViewportLayoutState;
|
export 'package:sky/widgets/mixed_viewport.dart' show MixedViewportLayoutState;
|
||||||
|
|
||||||
|
|
||||||
// The GestureEvent velocity properties are pixels/second, config min,max limits are pixels/ms
|
// The GestureEvent velocity properties are pixels/second, config min,max limits are pixels/ms
|
||||||
const double _kMillisecondsPerSecond = 1000.0;
|
const double _kMillisecondsPerSecond = 1000.0;
|
||||||
const double _kMinFlingVelocity = -config.kMaxFlingVelocity * _kMillisecondsPerSecond;
|
const double _kMinFlingVelocity = -config.kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||||
@ -48,11 +47,11 @@ abstract class Scrollable extends StatefulComponent {
|
|||||||
ValueAnimation<double> _toOffsetAnimation; // Started by scrollTo()
|
ValueAnimation<double> _toOffsetAnimation; // Started by scrollTo()
|
||||||
|
|
||||||
void initState() {
|
void initState() {
|
||||||
_toEndAnimation = new AnimatedSimulation(_tickScrollOffset);
|
_toEndAnimation = new AnimatedSimulation(_setScrollOffset);
|
||||||
_toOffsetAnimation = new ValueAnimation<double>()
|
_toOffsetAnimation = new ValueAnimation<double>()
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
AnimatedValue<double> offset = _toOffsetAnimation.variable;
|
AnimatedValue<double> offset = _toOffsetAnimation.variable;
|
||||||
scrollTo(offset.value);
|
_setScrollOffset(offset.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,8 +92,7 @@ abstract class Scrollable extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) {
|
Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) {
|
||||||
_stopToEndAnimation();
|
_stopAnimations();
|
||||||
_stopToOffsetAnimation();
|
|
||||||
_toOffsetAnimation
|
_toOffsetAnimation
|
||||||
..variable = new AnimatedValue<double>(scrollOffset,
|
..variable = new AnimatedValue<double>(scrollOffset,
|
||||||
end: newScrollOffset,
|
end: newScrollOffset,
|
||||||
@ -105,47 +103,46 @@ abstract class Scrollable extends StatefulComponent {
|
|||||||
return _toOffsetAnimation.play();
|
return _toOffsetAnimation.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stopToOffsetAnimation() {
|
void _stopAnimations() {
|
||||||
if (_toOffsetAnimation.isAnimating)
|
if (_toOffsetAnimation.isAnimating)
|
||||||
_toOffsetAnimation.stop();
|
_toOffsetAnimation.stop();
|
||||||
|
if (_toEndAnimation.isAnimating)
|
||||||
|
_toEndAnimation.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startToEndAnimation({ double velocity: 0.0 }) {
|
void _startToEndAnimation({ double velocity: 0.0 }) {
|
||||||
_stopToEndAnimation();
|
_stopAnimations();
|
||||||
_stopToOffsetAnimation();
|
|
||||||
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
|
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
|
||||||
if (simulation != null)
|
if (simulation != null)
|
||||||
_toEndAnimation.start(simulation);
|
_toEndAnimation.start(simulation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stopToEndAnimation() {
|
void didUnmount() {
|
||||||
_toEndAnimation.stop();
|
_stopAnimations();
|
||||||
|
super.didUnmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void didUnmount() {
|
void _setScrollOffset(double newScrollOffset) {
|
||||||
_stopToEndAnimation();
|
if (_scrollOffset == newScrollOffset)
|
||||||
_stopToOffsetAnimation();
|
return;
|
||||||
super.didUnmount();
|
setState(() {
|
||||||
|
_scrollOffset = newScrollOffset;
|
||||||
|
});
|
||||||
|
if (_listeners.length > 0)
|
||||||
|
_notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future scrollTo(double newScrollOffset, { Duration duration, Curve curve: ease }) {
|
Future scrollTo(double newScrollOffset, { Duration duration, Curve curve: ease }) {
|
||||||
if (newScrollOffset == _scrollOffset)
|
if (newScrollOffset == _scrollOffset)
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
|
|
||||||
Future result;
|
|
||||||
if (duration == null) {
|
if (duration == null) {
|
||||||
setState(() {
|
_stopAnimations();
|
||||||
_scrollOffset = newScrollOffset;
|
_setScrollOffset(newScrollOffset);
|
||||||
});
|
return new Future.value();
|
||||||
result = new Future.value();
|
|
||||||
} else {
|
|
||||||
result = _startToOffsetAnimation(newScrollOffset, duration, curve);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_listeners.length > 0)
|
return _startToOffsetAnimation(newScrollOffset, duration, curve);
|
||||||
_notifyListeners();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
|
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
|
||||||
@ -157,10 +154,6 @@ abstract class Scrollable extends StatefulComponent {
|
|||||||
_startToEndAnimation();
|
_startToEndAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tickScrollOffset(double value) {
|
|
||||||
scrollTo(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the event's velocity in pixels/second.
|
// Return the event's velocity in pixels/second.
|
||||||
double _eventVelocity(sky.GestureEvent event) {
|
double _eventVelocity(sky.GestureEvent event) {
|
||||||
double velocity = scrollDirection == ScrollDirection.horizontal
|
double velocity = scrollDirection == ScrollDirection.horizontal
|
||||||
@ -170,8 +163,7 @@ abstract class Scrollable extends StatefulComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EventDisposition _handlePointerDown(_) {
|
EventDisposition _handlePointerDown(_) {
|
||||||
_stopToEndAnimation();
|
_stopAnimations();
|
||||||
_stopToOffsetAnimation();
|
|
||||||
return EventDisposition.processed;
|
return EventDisposition.processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,10 +285,10 @@ class ScrollableViewport extends Scrollable {
|
|||||||
_updateScrollBehaviour();
|
_updateScrollBehaviour();
|
||||||
}
|
}
|
||||||
void _updateScrollBehaviour() {
|
void _updateScrollBehaviour() {
|
||||||
scrollBehavior.contentsSize = _childSize;
|
scrollTo(scrollBehavior.updateExtents(
|
||||||
scrollBehavior.containerSize = _viewportSize;
|
contentsExtents: _childSize,
|
||||||
if (scrollOffset > scrollBehavior.maxScrollOffset)
|
containerExtents: _viewportSize,
|
||||||
settleScrollOffset();
|
scrollOffset: scrollOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildContent() {
|
Widget buildContent() {
|
||||||
@ -375,6 +367,11 @@ abstract class ScrollableWidgetList extends Scrollable {
|
|||||||
itemExtent = source.itemExtent;
|
itemExtent = source.itemExtent;
|
||||||
super.syncFields(source); // update scrollDirection
|
super.syncFields(source); // update scrollDirection
|
||||||
|
|
||||||
|
if (itemCount != _previousItemCount) {
|
||||||
|
scrollBehaviorUpdateNeeded = true;
|
||||||
|
_previousItemCount = itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
if (scrollBehaviorUpdateNeeded)
|
if (scrollBehaviorUpdateNeeded)
|
||||||
_updateScrollBehavior();
|
_updateScrollBehavior();
|
||||||
}
|
}
|
||||||
@ -416,17 +413,14 @@ abstract class ScrollableWidgetList extends Scrollable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateScrollBehavior() {
|
void _updateScrollBehavior() {
|
||||||
scrollBehavior.containerSize = _containerExtent;
|
|
||||||
|
|
||||||
double contentsExtent = itemExtent * itemCount;
|
double contentsExtent = itemExtent * itemCount;
|
||||||
if (padding != null)
|
if (padding != null)
|
||||||
contentsExtent += _leadingPadding + _trailingPadding;
|
contentsExtent += _leadingPadding + _trailingPadding;
|
||||||
scrollBehavior.contentsSize = contentsExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateScrollOffset() {
|
scrollTo(scrollBehavior.updateExtents(
|
||||||
if (scrollOffset > scrollBehavior.maxScrollOffset)
|
contentsExtents: contentsExtent,
|
||||||
settleScrollOffset();
|
containerExtents: _containerExtent,
|
||||||
|
scrollOffset: scrollOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset _toOffset(double value) {
|
Offset _toOffset(double value) {
|
||||||
@ -439,7 +433,6 @@ abstract class ScrollableWidgetList extends Scrollable {
|
|||||||
if (itemCount != _previousItemCount) {
|
if (itemCount != _previousItemCount) {
|
||||||
_previousItemCount = itemCount;
|
_previousItemCount = itemCount;
|
||||||
_updateScrollBehavior();
|
_updateScrollBehavior();
|
||||||
_updateScrollOffset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double paddedScrollOffset = scrollOffset;
|
double paddedScrollOffset = scrollOffset;
|
||||||
@ -643,18 +636,19 @@ class ScrollableMixedWidgetList extends Scrollable {
|
|||||||
OverscrollBehavior get scrollBehavior => super.scrollBehavior;
|
OverscrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||||
|
|
||||||
void _handleSizeChanged(Size newSize) {
|
void _handleSizeChanged(Size newSize) {
|
||||||
scrollBehavior.containerSize = newSize.height;
|
scrollBy(scrollBehavior.updateExtents(
|
||||||
|
containerExtents: newSize.height,
|
||||||
|
scrollOffset: scrollOffset
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleLayoutChanged() {
|
void _handleLayoutChanged() {
|
||||||
if (layoutState.didReachLastChild) {
|
double newScrollOffset = scrollBehavior.updateExtents(
|
||||||
scrollBehavior.contentsSize = layoutState.contentsSize;
|
contentsExtents: layoutState.didReachLastChild ? layoutState.contentsSize : double.INFINITY,
|
||||||
if (_contentsChanged && scrollOffset > scrollBehavior.maxScrollOffset) {
|
scrollOffset: scrollOffset);
|
||||||
|
if (_contentsChanged) {
|
||||||
_contentsChanged = false;
|
_contentsChanged = false;
|
||||||
settleScrollOffset();
|
scrollTo(newScrollOffset);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scrollBehavior.contentsSize = double.INFINITY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ class TabBar extends Scrollable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
double _centeredTabScrollOffset(int tabIndex) {
|
double _centeredTabScrollOffset(int tabIndex) {
|
||||||
double viewportWidth = scrollBehavior.containerSize;
|
double viewportWidth = scrollBehavior.containerExtents;
|
||||||
return (_tabRect(tabIndex).left + _tabWidths[tabIndex] / 2.0 - viewportWidth / 2.0)
|
return (_tabRect(tabIndex).left + _tabWidths[tabIndex] / 2.0 - viewportWidth / 2.0)
|
||||||
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
|
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
|
||||||
}
|
}
|
||||||
@ -495,8 +495,9 @@ class TabBar extends Scrollable {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tabBarSize = tabBarSize;
|
_tabBarSize = tabBarSize;
|
||||||
_tabWidths = tabWidths;
|
_tabWidths = tabWidths;
|
||||||
scrollBehavior.containerSize = _tabBarSize.width;
|
scrollBehavior.updateExtents(
|
||||||
scrollBehavior.contentsSize = _tabWidths.reduce((sum, width) => sum + width);
|
containerExtents: _tabBarSize.width,
|
||||||
|
contentsExtents: _tabWidths.reduce((sum, width) => sum + width));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user