Commit 072cce88 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Reparameterize Scrollable2 (#7853)

This patch makes a number of changes to how you can configure a
Scrollable2:

 - The ScrollPhysics is now responsible for creating the ScrollPosition.
   You can override the ScrollPhysics by supplying a `physics` argument
   to `Scrollable`, and the new physics you supply will be applied to
   the default physics inherited from the ScrollBehavior.

 - This patch removes the ScrollPosition/AbsoluteScrollPosition split as
   all clients were operating in pixels anyway and the split made the
   code very difficult to follow.

 - ScrollPosition no longer depends directly on Scrollable2State.
   Instead, it depends on an abstract interface that Scrollable2State
   implements. This change has two benefits:

    a) It removes the circular dependency between ScrollPosition and
       Scrollable2State, which lets us split the code for these classes
       (and several other classes that got wrapped up in that cycle) into
       separate libraries for easier maintenance.

    b) ScrollPosition is no longer bound to Scrollable2, which means you
       could use the behavior machinery to drive other sorts of widgets.
       For example, we could use it to drive Scrollabe1 if we wanted.
parent 475e7ce9
...@@ -214,7 +214,7 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate { ...@@ -214,7 +214,7 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
bool updateShouldNotify(ScrollConfigurationDelegate old) => false; bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
} }
class _MaterialScrollBehavior extends ViewportScrollBehavior { class _MaterialScrollBehavior extends ScrollBehavior2 {
@override @override
TargetPlatform getPlatform(BuildContext context) { TargetPlatform getPlatform(BuildContext context) {
return Theme.of(context).platform; return Theme.of(context).platform;
...@@ -308,7 +308,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -308,7 +308,7 @@ class _MaterialAppState extends State<MaterialApp> {
); );
return new ScrollConfiguration2( return new ScrollConfiguration2(
delegate: new _MaterialScrollBehavior(), behavior: new _MaterialScrollBehavior(),
child: result child: result
); );
} }
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// 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.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE // DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:async' show Timer; import 'dart:async' show Timer;
import 'dart:math' as math; import 'dart:math' as math;
......
...@@ -180,8 +180,9 @@ class _ScrollbarPainter extends CustomPainter { ...@@ -180,8 +180,9 @@ class _ScrollbarPainter extends CustomPainter {
} }
} }
////////////////////////////////////////////////////////////////////////////////
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE // DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
const double _kMinScrollbarThumbExtent = 18.0; const double _kMinScrollbarThumbExtent = 18.0;
const double _kScrollbarThumbGirth = 6.0; const double _kScrollbarThumbGirth = 6.0;
......
...@@ -30,6 +30,8 @@ import 'tolerance.dart'; ...@@ -30,6 +30,8 @@ import 'tolerance.dart';
/// should establish a convention and use that convention consistently with all /// should establish a convention and use that convention consistently with all
/// related objects. /// related objects.
abstract class Simulation { abstract class Simulation {
Simulation({ this.tolerance: Tolerance.defaultTolerance });
/// The position of the object in the simulation at the given time. /// The position of the object in the simulation at the given time.
double x(double time); double x(double time);
...@@ -46,7 +48,7 @@ abstract class Simulation { ...@@ -46,7 +48,7 @@ abstract class Simulation {
/// but once the difference from the value at a particular time and the /// but once the difference from the value at a particular time and the
/// asymptote itself could not be seen, it would be pointless to continue. The /// asymptote itself could not be seen, it would be pointless to continue. The
/// tolerance defines how to determine if the difference could not be seen. /// tolerance defines how to determine if the difference could not be seen.
Tolerance tolerance = Tolerance.defaultTolerance; Tolerance tolerance;
@override @override
String toString() => '$runtimeType'; String toString() => '$runtimeType';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'simulation.dart'; import 'simulation.dart';
import 'tolerance.dart';
import 'utils.dart'; import 'utils.dart';
/// Structure that describes a spring's constants. /// Structure that describes a spring's constants.
...@@ -86,12 +87,14 @@ class SpringSimulation extends Simulation { ...@@ -86,12 +87,14 @@ class SpringSimulation extends Simulation {
/// arbitrary unit of length, and T is the time unit used for driving the /// arbitrary unit of length, and T is the time unit used for driving the
/// [SpringSimulation]. /// [SpringSimulation].
SpringSimulation( SpringSimulation(
SpringDescription desc, SpringDescription spring,
double start, double start,
double end, double end,
double velocity double velocity, {
) : _endPosition = end, Tolerance tolerance: Tolerance.defaultTolerance,
_solution = new _SpringSolution(desc, start - end, velocity); }) : _endPosition = end,
_solution = new _SpringSolution(spring, start - end, velocity),
super(tolerance: tolerance);
final double _endPosition; final double _endPosition;
final _SpringSolution _solution; final _SpringSolution _solution;
...@@ -124,11 +127,12 @@ class ScrollSpringSimulation extends SpringSimulation { ...@@ -124,11 +127,12 @@ class ScrollSpringSimulation extends SpringSimulation {
/// See the [new SpringSimulation] constructor on the superclass for a /// See the [new SpringSimulation] constructor on the superclass for a
/// discussion of the arguments' units. /// discussion of the arguments' units.
ScrollSpringSimulation( ScrollSpringSimulation(
SpringDescription desc, SpringDescription spring,
double start, double start,
double end, double end,
double velocity double velocity, {
) : super(desc, start, end, velocity); Tolerance tolerance: Tolerance.defaultTolerance,
}) : super(spring, start, end, velocity, tolerance: tolerance);
@override @override
double x(double time) => isDone(time) ? _endPosition : super.x(time); double x(double time) => isDone(time) ? _endPosition : super.x(time);
...@@ -139,22 +143,22 @@ class ScrollSpringSimulation extends SpringSimulation { ...@@ -139,22 +143,22 @@ class ScrollSpringSimulation extends SpringSimulation {
abstract class _SpringSolution { abstract class _SpringSolution {
factory _SpringSolution( factory _SpringSolution(
SpringDescription desc, SpringDescription spring,
double initialPosition, double initialPosition,
double initialVelocity double initialVelocity
) { ) {
assert(desc != null); assert(spring != null);
assert(desc.mass != null); assert(spring.mass != null);
assert(desc.springConstant != null); assert(spring.springConstant != null);
assert(desc.damping != null); assert(spring.damping != null);
assert(initialPosition != null); assert(initialPosition != null);
assert(initialVelocity != null); assert(initialVelocity != null);
double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.springConstant;
if (cmk == 0.0) if (cmk == 0.0)
return new _CriticalSolution(desc, initialPosition, initialVelocity); return new _CriticalSolution(spring, initialPosition, initialVelocity);
if (cmk > 0.0) if (cmk > 0.0)
return new _OverdampedSolution(desc, initialPosition, initialVelocity); return new _OverdampedSolution(spring, initialPosition, initialVelocity);
return new _UnderdampedSolution(desc, initialPosition, initialVelocity); return new _UnderdampedSolution(spring, initialPosition, initialVelocity);
} }
double x(double time); double x(double time);
...@@ -164,11 +168,11 @@ abstract class _SpringSolution { ...@@ -164,11 +168,11 @@ abstract class _SpringSolution {
class _CriticalSolution implements _SpringSolution { class _CriticalSolution implements _SpringSolution {
factory _CriticalSolution( factory _CriticalSolution(
SpringDescription desc, SpringDescription spring,
double distance, double distance,
double velocity double velocity
) { ) {
final double r = -desc.damping / (2.0 * desc.mass); final double r = -spring.damping / (2.0 * spring.mass);
final double c1 = distance; final double c1 = distance;
final double c2 = velocity / (r * distance); final double c2 = velocity / (r * distance);
return new _CriticalSolution.withArgs(r, c1, c2); return new _CriticalSolution.withArgs(r, c1, c2);
...@@ -198,13 +202,13 @@ class _CriticalSolution implements _SpringSolution { ...@@ -198,13 +202,13 @@ class _CriticalSolution implements _SpringSolution {
class _OverdampedSolution implements _SpringSolution { class _OverdampedSolution implements _SpringSolution {
factory _OverdampedSolution( factory _OverdampedSolution(
SpringDescription desc, SpringDescription spring,
double distance, double distance,
double velocity double velocity
) { ) {
final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.springConstant;
final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass); final double r1 = (-spring.damping - math.sqrt(cmk)) / (2.0 * spring.mass);
final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass); final double r2 = (-spring.damping + math.sqrt(cmk)) / (2.0 * spring.mass);
final double c2 = (velocity - r1 * distance) / (r2 - r1); final double c2 = (velocity - r1 * distance) / (r2 - r1);
final double c1 = distance - c2; final double c1 = distance - c2;
return new _OverdampedSolution.withArgs(r1, r2, c1, c2); return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
...@@ -236,13 +240,13 @@ class _OverdampedSolution implements _SpringSolution { ...@@ -236,13 +240,13 @@ class _OverdampedSolution implements _SpringSolution {
class _UnderdampedSolution implements _SpringSolution { class _UnderdampedSolution implements _SpringSolution {
factory _UnderdampedSolution( factory _UnderdampedSolution(
SpringDescription desc, SpringDescription spring,
double distance, double distance,
double velocity double velocity
) { ) {
final double w = math.sqrt(4.0 * desc.mass * desc.springConstant - final double w = math.sqrt(4.0 * spring.mass * spring.springConstant -
desc.damping * desc.damping) / (2.0 * desc.mass); spring.damping * spring.damping) / (2.0 * spring.mass);
final double r = -(desc.damping / 2.0 * desc.mass); final double r = -(spring.damping / 2.0 * spring.mass);
final double c1 = distance; final double c1 = distance;
final double c2 = (velocity - r * distance) / w; final double c2 = (velocity - r * distance) / w;
return new _UnderdampedSolution.withArgs(w, r, c1, c2); return new _UnderdampedSolution.withArgs(w, r, c1, c2);
......
...@@ -62,8 +62,8 @@ class GlowingOverscrollIndicator extends StatefulWidget { ...@@ -62,8 +62,8 @@ class GlowingOverscrollIndicator extends StatefulWidget {
/// widget. /// widget.
/// ///
/// Typically a [GlowingOverscrollIndicator] is created by a /// Typically a [GlowingOverscrollIndicator] is created by a
/// [ScrollBehavior2.wrap] method, in which case the child is usually the one /// [ScrollBehavior2.buildViewportChrome] method, in which case
/// provided as an argument to that method. /// the child is usually the one provided as an argument to that method.
final Widget child; final Widget child;
@override @override
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/physics.dart';
import 'scroll_absolute.dart';
class PageScrollPhysics extends ScrollPhysicsProxy {
const PageScrollPhysics({
ScrollPhysics parent,
this.springDescription,
}) : super(parent);
final SpringDescription springDescription;
@override
PageScrollPhysics applyTo(ScrollPhysics parent) {
return new PageScrollPhysics(
parent: parent,
springDescription: springDescription,
);
}
double _roundToPage(AbsoluteScrollPosition position, double pixels, double pageSize) {
final int index = (pixels + pageSize / 2.0) ~/ pageSize;
return (pageSize * index).clamp(position.minScrollExtent, position.maxScrollExtent);
}
double _getTargetPixels(AbsoluteScrollPosition position, double velocity) {
final double pageSize = position.viewportDimension;
if (velocity < -position.scrollTolerances.velocity)
return _roundToPage(position, position.pixels - pageSize / 2.0, pageSize);
if (velocity > position.scrollTolerances.velocity)
return _roundToPage(position, position.pixels + pageSize / 2.0, pageSize);
return _roundToPage(position, position.pixels, pageSize);
}
@override
Simulation createBallisticSimulation(AbsoluteScrollPosition position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final double target = _getTargetPixels(position, velocity);
return new ScrollSpringSimulation(scrollSpring, position.pixels, target, velocity);
}
}
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// 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.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE // DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math; import 'dart:math' as math;
......
...@@ -2,12 +2,86 @@ ...@@ -2,12 +2,86 @@
// 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.
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart'; import 'framework.dart';
import 'scroll_behavior.dart'; import 'scroll_behavior.dart';
import 'scroll_physics.dart';
import 'overscroll_indicator.dart';
class ScrollBehavior2 {
const ScrollBehavior2();
/// The platform whose scroll physics should be implemented.
///
/// Defaults to the current platform.
TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;
/// The color to use for the glow effect when [platform] indicates a platform
/// that uses a [GlowingOverscrollIndicator].
///
/// Defaults to white.
Color getGlowColor(BuildContext context) => const Color(0xFFFFFFFF);
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return new GlowingOverscrollIndicator(
child: child,
axisDirection: axisDirection,
color: getGlowColor(context),
);
}
return null;
}
/// The scroll physics to use for the given platform.
///
/// Used by [createScrollPosition] to get the scroll physics for newly created
/// scroll positions.
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
return const BouncingScrollPhysics();
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return const ClampingScrollPhysics();
}
return null;
}
bool shouldNotify(@checked ScrollBehavior2 oldDelegate) => false;
}
class ScrollConfiguration2 extends InheritedWidget {
const ScrollConfiguration2({
Key key,
@required this.behavior,
@required Widget child,
}) : super(key: key, child: child);
final ScrollBehavior2 behavior;
static ScrollBehavior2 of(BuildContext context) {
final ScrollConfiguration2 configuration = context.inheritFromWidgetOfExactType(ScrollConfiguration2);
return configuration?.behavior ?? const ScrollBehavior2();
}
@override
bool updateShouldNotify(ScrollConfiguration2 old) {
assert(behavior != null);
return behavior.runtimeType != old.behavior.runtimeType
|| behavior.shouldNotify(old.behavior);
}
}
////////////////////////////////////////////////////////////////////////////////
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
/// Controls how [Scrollable] widgets in a subtree behave. /// Controls how [Scrollable] widgets in a subtree behave.
/// ///
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/physics.dart';
import 'overscroll_indicator.dart';
import 'scroll_simulation.dart';
import 'scroll_position.dart';
// The ScrollPhysics base class is defined in scroll_position.dart because it
// has as circular dependency with ScrollPosition.
export 'scroll_position.dart' show ScrollPhysics;
/// Scroll physics for environments that allow the scroll offset to go beyond
/// the bounds of the content, but then bounce the content back to the edge of
/// those bounds.
///
/// This is the behavior typically seen on iOS.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the iOS component of
/// its scroll behavior.
/// * [ClampingScrollPhysics], which is the analogous physics for Android's
/// clamping behavior.
class BouncingScrollPhysics extends ScrollPhysics {
const BouncingScrollPhysics({ ScrollPhysics parent }) : super(parent);
@override
BouncingScrollPhysics applyTo(ScrollPhysics parent) => new BouncingScrollPhysics(parent: parent);
/// The multiple applied to overscroll to make it appear that scrolling past
/// the edge of the scrollable contents is harder than scrolling the list.
///
/// By default this is 0.5, meaning that overscroll is twice as hard as normal
/// scroll.
double get frictionFactor => 0.5;
@override
double applyPhysicsToUserOffset(ScrollPosition position, double offset) {
assert(offset != 0.0);
assert(position.minScrollExtent <= position.maxScrollExtent);
if (offset > 0.0)
return _applyFriction(position.pixels, position.minScrollExtent, position.maxScrollExtent, offset, frictionFactor);
return -_applyFriction(-position.pixels, -position.maxScrollExtent, -position.minScrollExtent, -offset, frictionFactor);
}
static double _applyFriction(double start, double lowLimit, double highLimit, double delta, double gamma) {
assert(lowLimit <= highLimit);
assert(delta > 0.0);
double total = 0.0;
if (start < lowLimit) {
double distanceToLimit = lowLimit - start;
double deltaToLimit = distanceToLimit / gamma;
if (delta < deltaToLimit)
return total + delta * gamma;
total += distanceToLimit;
delta -= deltaToLimit;
}
return total + delta;
}
@override
Simulation createBallisticSimulation(ScrollPosition position, double velocity) {
final Tolerance tolerance = this.tolerance;
if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
return new BouncingScrollSimulation(
spring: spring,
position: position.pixels,
velocity: velocity,
leadingExtent: position.minScrollExtent,
trailingExtent: position.maxScrollExtent,
)..tolerance = tolerance;
}
return null;
}
}
/// Scroll physics for environments that prevent the scroll offset from reaching
/// beyond the bounds of the content.
///
/// This is the behavior typically seen on Android.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the Android component
/// of its scroll behavior.
/// * [BouncingScrollPhysics], which is the analogous physics for iOS' bouncing
/// behavior.
/// * [GlowingOverscrollIndicator], which is used by [ViewportScrollBehavior] to
/// provide the glowing effect that is usually found with this clamping effect
/// on Android.
class ClampingScrollPhysics extends ScrollPhysics {
const ClampingScrollPhysics({ ScrollPhysics parent }) : super(parent);
@override
ClampingScrollPhysics applyTo(ScrollPhysics parent) => new ClampingScrollPhysics(parent: parent);
@override
double applyBoundaryConditions(ScrollPosition position, double value) {
assert(value != position.pixels);
if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
return value - position.pixels;
if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
return 0.0;
}
@override
Simulation createBallisticSimulation(ScrollPosition position, double velocity) {
final Tolerance tolerance = this.tolerance;
if (position.outOfRange) {
double end;
if (position.pixels > position.maxScrollExtent)
end = position.maxScrollExtent;
if (position.pixels < position.minScrollExtent)
end = position.minScrollExtent;
assert(end != null);
return new ScrollSpringSimulation(
spring,
position.pixels,
position.maxScrollExtent,
math.min(0.0, velocity),
tolerance: tolerance
);
}
if (!position.atEdge && velocity.abs() >= tolerance.velocity) {
return new ClampingScrollSimulation(
position: position.pixels,
velocity: velocity,
tolerance: tolerance,
);
}
return null;
}
}
class PageScrollPhysics extends ScrollPhysics {
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent);
@override
PageScrollPhysics applyTo(ScrollPhysics parent) => new PageScrollPhysics(parent: parent);
double _roundToPage(ScrollPosition position, double pixels, double pageSize) {
final int index = (pixels + pageSize / 2.0) ~/ pageSize;
return (pageSize * index).clamp(position.minScrollExtent, position.maxScrollExtent);
}
double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) {
final double pageSize = position.viewportDimension;
if (velocity < -tolerance.velocity)
return _roundToPage(position, position.pixels - pageSize / 2.0, pageSize);
if (velocity > tolerance.velocity)
return _roundToPage(position, position.pixels + pageSize / 2.0, pageSize);
return _roundToPage(position, position.pixels, pageSize);
}
@override
Simulation createBallisticSimulation(ScrollPosition position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
return new ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
}
}
...@@ -116,7 +116,8 @@ class ClampingScrollSimulation extends Simulation { ...@@ -116,7 +116,8 @@ class ClampingScrollSimulation extends Simulation {
@required this.position, @required this.position,
@required this.velocity, @required this.velocity,
this.friction: 0.015, this.friction: 0.015,
}) { Tolerance tolerance: Tolerance.defaultTolerance,
}) : super(tolerance: tolerance) {
_scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff _scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff
_duration = _flingDuration(velocity); _duration = _flingDuration(velocity);
_distance = _flingDistance(velocity); _distance = _flingDistance(velocity);
...@@ -196,7 +197,9 @@ class ClampingScrollSimulation extends Simulation { ...@@ -196,7 +197,9 @@ class ClampingScrollSimulation extends Simulation {
} }
} }
////////////////////////////////////////////////////////////////////////////////
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE // DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
final SpringDescription _kScrollSpring = new SpringDescription.withDampingRatio(mass: 0.5, springConstant: 100.0, ratio: 1.1); final SpringDescription _kScrollSpring = new SpringDescription.withDampingRatio(mass: 0.5, springConstant: 100.0, ratio: 1.1);
final double _kDrag = 0.025; final double _kDrag = 0.025;
......
...@@ -7,8 +7,8 @@ import 'package:meta/meta.dart'; ...@@ -7,8 +7,8 @@ import 'package:meta/meta.dart';
import 'framework.dart'; import 'framework.dart';
import 'basic.dart'; import 'basic.dart';
import 'page_scroll_physics.dart'; import 'scroll_physics.dart';
import 'scroll_absolute.dart'; import 'scroll_position.dart';
import 'scrollable.dart'; import 'scrollable.dart';
import 'sliver.dart'; import 'sliver.dart';
import 'viewport.dart'; import 'viewport.dart';
......
...@@ -46,10 +46,11 @@ export 'src/widgets/performance_overlay.dart'; ...@@ -46,10 +46,11 @@ export 'src/widgets/performance_overlay.dart';
export 'src/widgets/placeholder.dart'; export 'src/widgets/placeholder.dart';
export 'src/widgets/raw_keyboard_listener.dart'; export 'src/widgets/raw_keyboard_listener.dart';
export 'src/widgets/routes.dart'; export 'src/widgets/routes.dart';
export 'src/widgets/scroll_absolute.dart';
export 'src/widgets/scroll_behavior.dart'; export 'src/widgets/scroll_behavior.dart';
export 'src/widgets/scroll_configuration.dart'; export 'src/widgets/scroll_configuration.dart';
export 'src/widgets/scroll_notification.dart'; export 'src/widgets/scroll_notification.dart';
export 'src/widgets/scroll_physics.dart';
export 'src/widgets/scroll_position.dart';
export 'src/widgets/scroll_simulation.dart'; export 'src/widgets/scroll_simulation.dart';
export 'src/widgets/scroll_view.dart'; export 'src/widgets/scroll_view.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
......
...@@ -72,7 +72,7 @@ void main() { ...@@ -72,7 +72,7 @@ void main() {
log.clear(); log.clear();
Scrollable2State state = tester.state(find.byType(Scrollable2)); Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position; ScrollPosition position = state.position;
position.jumpTo(2025.0); position.jumpTo(2025.0);
expect(log, isEmpty); expect(log, isEmpty);
......
...@@ -204,12 +204,14 @@ void main() { ...@@ -204,12 +204,14 @@ void main() {
RenderObject painter; RenderObject painter;
await tester.pumpWidget( await tester.pumpWidget(
new TestScrollable( new ScrollConfiguration2(
axisDirection: AxisDirection.left, behavior: new TestScrollBehavior1(),
scrollBehavior: new TestScrollBehavior1(), child: new TestScrollable(
slivers: <Widget>[ axisDirection: AxisDirection.left,
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)), slivers: <Widget>[
], new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
),
), ),
); );
painter = tester.renderObject(find.byType(CustomPaint)); painter = tester.renderObject(find.byType(CustomPaint));
...@@ -219,12 +221,14 @@ void main() { ...@@ -219,12 +221,14 @@ void main() {
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1)); await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
await tester.pumpWidget( await tester.pumpWidget(
new TestScrollable( new ScrollConfiguration2(
axisDirection: AxisDirection.right, behavior: new TestScrollBehavior2(),
scrollBehavior: new TestScrollBehavior2(), child: new TestScrollable(
slivers: <Widget>[ axisDirection: AxisDirection.right,
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)), slivers: <Widget>[
], new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
),
), ),
); );
painter = tester.renderObject(find.byType(CustomPaint)); painter = tester.renderObject(find.byType(CustomPaint));
...@@ -234,14 +238,14 @@ void main() { ...@@ -234,14 +238,14 @@ void main() {
}); });
} }
class TestScrollBehavior1 extends ViewportScrollBehavior { class TestScrollBehavior1 extends ScrollBehavior2 {
@override @override
Color getGlowColor(BuildContext context) { Color getGlowColor(BuildContext context) {
return const Color(0xFF00FF00); return const Color(0xFF00FF00);
} }
} }
class TestScrollBehavior2 extends ViewportScrollBehavior { class TestScrollBehavior2 extends ScrollBehavior2 {
@override @override
Color getGlowColor(BuildContext context) { Color getGlowColor(BuildContext context) {
return const Color(0xFF0000FF); return const Color(0xFF0000FF);
......
...@@ -158,7 +158,7 @@ void main() { ...@@ -158,7 +158,7 @@ void main() {
Scrollable2State state = tester.state(find.byType(Scrollable2)); Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position; ScrollPosition position = state.position;
position.jumpTo(3025.0); position.jumpTo(3025.0);
expect(log, isEmpty); expect(log, isEmpty);
......
...@@ -10,13 +10,13 @@ import 'test_widgets.dart'; ...@@ -10,13 +10,13 @@ import 'test_widgets.dart';
class TestScrollPosition extends ScrollPosition { class TestScrollPosition extends ScrollPosition {
TestScrollPosition( TestScrollPosition(
this.extentMultiplier, TestScrollPhysics physics,
Scrollable2State state, AbstractScrollState state,
Tolerance scrollTolerances,
ScrollPosition oldPosition, ScrollPosition oldPosition,
) : _pixels = 100.0, super(state, scrollTolerances, oldPosition); ) : _pixels = 100.0, super(physics, state, oldPosition);
final double extentMultiplier; @override
TestScrollPhysics get physics => super.physics;
double _min, _viewport, _max, _pixels; double _min, _viewport, _max, _pixels;
...@@ -27,7 +27,7 @@ class TestScrollPosition extends ScrollPosition { ...@@ -27,7 +27,7 @@ class TestScrollPosition extends ScrollPosition {
double setPixels(double value) { double setPixels(double value) {
double oldPixels = _pixels; double oldPixels = _pixels;
_pixels = value; _pixels = value;
dispatchNotification(activity.createScrollUpdateNotification(state, _pixels - oldPixels)); state.dispatchNotification(activity.createScrollUpdateNotification(state, _pixels - oldPixels));
return 0.0; return 0.0;
} }
...@@ -56,9 +56,9 @@ class TestScrollPosition extends ScrollPosition { ...@@ -56,9 +56,9 @@ class TestScrollPosition extends ScrollPosition {
double afterExtent = _max - _pixels; double afterExtent = _max - _pixels;
if (insideExtent > 0.0) { if (insideExtent > 0.0) {
return new ScrollableMetrics( return new ScrollableMetrics(
extentBefore: extentMultiplier * beforeExtent / insideExtent, extentBefore: physics.extentMultiplier * beforeExtent / insideExtent,
extentInside: extentMultiplier, extentInside: physics.extentMultiplier,
extentAfter: extentMultiplier * afterExtent / insideExtent, extentAfter: physics.extentMultiplier * afterExtent / insideExtent,
); );
} else { } else {
return new ScrollableMetrics( return new ScrollableMetrics(
...@@ -70,17 +70,36 @@ class TestScrollPosition extends ScrollPosition { ...@@ -70,17 +70,36 @@ class TestScrollPosition extends ScrollPosition {
} }
} }
class TestScrollPhysics extends ScrollPhysics {
const TestScrollPhysics({ ScrollPhysics parent, this.extentMultiplier }) : super(parent);
final double extentMultiplier;
@override
TestScrollPhysics applyTo(ScrollPhysics parent) {
return new TestScrollPhysics(parent: parent, extentMultiplier: extentMultiplier);
}
@override
ScrollPosition createScrollPosition(ScrollPhysics physics, AbstractScrollState state, ScrollPosition oldPosition) {
return new TestScrollPosition(physics, state, oldPosition);
}
}
class TestScrollBehavior extends ScrollBehavior2 { class TestScrollBehavior extends ScrollBehavior2 {
TestScrollBehavior(this.extentMultiplier); TestScrollBehavior(this.extentMultiplier);
final double extentMultiplier; final double extentMultiplier;
@override @override
Widget wrap(BuildContext context, Widget child, AxisDirection axisDirection) => child; ScrollPhysics getScrollPhysics(BuildContext context) {
return new TestScrollPhysics(
extentMultiplier: extentMultiplier
).applyTo(super.getScrollPhysics(context));
}
@override @override
ScrollPosition createScrollPosition(BuildContext context, Scrollable2State state, ScrollPosition oldPosition, ScrollPhysics physics) { Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) => child;
return new TestScrollPosition(extentMultiplier, state, ViewportScrollBehavior.defaultScrollTolerances, oldPosition);
}
@override @override
bool shouldNotify(TestScrollBehavior oldDelegate) { bool shouldNotify(TestScrollBehavior oldDelegate) {
...@@ -90,20 +109,24 @@ class TestScrollBehavior extends ScrollBehavior2 { ...@@ -90,20 +109,24 @@ class TestScrollBehavior extends ScrollBehavior2 {
void main() { void main() {
testWidgets('Changing the scroll behavior dynamically', (WidgetTester tester) async { testWidgets('Changing the scroll behavior dynamically', (WidgetTester tester) async {
await tester.pumpWidget(new TestScrollable( await tester.pumpWidget(new ScrollConfiguration2(
scrollBehavior: new TestScrollBehavior(1.0), behavior: new TestScrollBehavior(1.0),
slivers: <Widget>[ child: new TestScrollable(
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)), slivers: <Widget>[
], new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
),
)); ));
Scrollable2State state = tester.state(find.byType(Scrollable2)); Scrollable2State state = tester.state(find.byType(Scrollable2));
expect(state.position.getMetrics().extentInside, 1.0); expect(state.position.getMetrics().extentInside, 1.0);
await tester.pumpWidget(new TestScrollable( await tester.pumpWidget(new ScrollConfiguration2(
scrollBehavior: new TestScrollBehavior(2.0), behavior: new TestScrollBehavior(2.0),
slivers: <Widget>[ child: new TestScrollable(
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)), slivers: <Widget>[
], new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
),
)); ));
expect(state.position.getMetrics().extentInside, 2.0); expect(state.position.getMetrics().extentInside, 2.0);
}); });
......
...@@ -32,7 +32,7 @@ double getScrollOffset(WidgetTester tester) { ...@@ -32,7 +32,7 @@ double getScrollOffset(WidgetTester tester) {
void resetScrollOffset(WidgetTester tester) { void resetScrollOffset(WidgetTester tester) {
RenderViewport2 viewport = tester.renderObject(find.byType(Viewport2)); RenderViewport2 viewport = tester.renderObject(find.byType(Viewport2));
AbsoluteScrollPosition position = viewport.offset; ScrollPosition position = viewport.offset;
position.jumpTo(0.0); position.jumpTo(0.0);
} }
......
...@@ -37,7 +37,7 @@ void main() { ...@@ -37,7 +37,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
final double max = bigHeight * 2.0 + new TestDelegate().maxExtent - 600.0; // 600 is the height of the test viewport final double max = bigHeight * 2.0 + new TestDelegate().maxExtent - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0); assert(max < 10000.0);
expect(max, 1600.0); expect(max, 1600.0);
...@@ -65,7 +65,7 @@ void main() { ...@@ -65,7 +65,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
verifyPaintPosition(key1, new Offset(0.0, 0.0), true); verifyPaintPosition(key1, new Offset(0.0, 0.0), true);
verifyPaintPosition(key2, new Offset(0.0, 600.0), false); verifyPaintPosition(key2, new Offset(0.0, 600.0), false);
...@@ -135,7 +135,7 @@ void main() { ...@@ -135,7 +135,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
verifyPaintPosition(key1, new Offset(0.0, 0.0), true); verifyPaintPosition(key1, new Offset(0.0, 0.0), true);
verifyPaintPosition(key2, new Offset(0.0, 600.0), false); verifyPaintPosition(key2, new Offset(0.0, 600.0), false);
...@@ -168,7 +168,7 @@ void main() { ...@@ -168,7 +168,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
verifyPaintPosition(key1, new Offset(0.0, 0.0), true); verifyPaintPosition(key1, new Offset(0.0, 0.0), true);
verifyPaintPosition(key2, new Offset(0.0, 600.0), false); verifyPaintPosition(key2, new Offset(0.0, 600.0), false);
......
...@@ -40,7 +40,7 @@ void main() { ...@@ -40,7 +40,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
final double max = bigHeight * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport final double max = bigHeight * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0); assert(max < 10000.0);
expect(max, 1450.0); expect(max, 1450.0);
...@@ -74,7 +74,7 @@ void main() { ...@@ -74,7 +74,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
verifyPaintPosition(key1, new Offset(0.0, 0.0), true); verifyPaintPosition(key1, new Offset(0.0, 0.0), true);
verifyPaintPosition(key2, new Offset(0.0, 550.0), true); verifyPaintPosition(key2, new Offset(0.0, 550.0), true);
verifyPaintPosition(key3, new Offset(0.0, 600.0), false); verifyPaintPosition(key3, new Offset(0.0, 600.0), false);
...@@ -164,7 +164,7 @@ void main() { ...@@ -164,7 +164,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
final double max = bigHeight * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport final double max = bigHeight * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0); assert(max < 10000.0);
expect(max, 1750.0); expect(max, 1750.0);
......
...@@ -31,7 +31,7 @@ void main() { ...@@ -31,7 +31,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
final double max = RenderBigSliver.height * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport final double max = RenderBigSliver.height * 3.0 + new TestDelegate().maxExtent * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0); assert(max < 10000.0);
expect(max, 1450.0); expect(max, 1450.0);
...@@ -64,7 +64,7 @@ void main() { ...@@ -64,7 +64,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
position.animate(to: RenderBigSliver.height + delegate.maxExtent - 5.0, curve: Curves.linear, duration: const Duration(minutes: 1)); position.animate(to: RenderBigSliver.height + delegate.maxExtent - 5.0, curve: Curves.linear, duration: const Duration(minutes: 1));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 1000)); await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 1000));
RenderBox box = tester.renderObject<RenderBox>(find.byType(Container)); RenderBox box = tester.renderObject<RenderBox>(find.byType(Container));
......
...@@ -33,7 +33,7 @@ void main() { ...@@ -33,7 +33,7 @@ void main() {
], ],
), ),
); );
AbsoluteScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position; ScrollPosition position = tester.state<Scrollable2State>(find.byType(Scrollable2)).position;
final double max = RenderBigSliver.height * 3.0 + (RenderOverlappingSliver.totalHeight) * 2.0 - 600.0; // 600 is the height of the test viewport final double max = RenderBigSliver.height * 3.0 + (RenderOverlappingSliver.totalHeight) * 2.0 - 600.0; // 600 is the height of the test viewport
assert(max < 10000.0); assert(max < 10000.0);
expect(max, 1450.0); expect(max, 1450.0);
......
...@@ -65,7 +65,6 @@ class TestScrollable extends StatelessWidget { ...@@ -65,7 +65,6 @@ class TestScrollable extends StatelessWidget {
this.physics, this.physics,
this.anchor: 0.0, this.anchor: 0.0,
this.center, this.center,
this.scrollBehavior,
this.slivers: const <Widget>[], this.slivers: const <Widget>[],
}) { }) {
assert(slivers != null); assert(slivers != null);
...@@ -79,8 +78,6 @@ class TestScrollable extends StatelessWidget { ...@@ -79,8 +78,6 @@ class TestScrollable extends StatelessWidget {
final Key center; final Key center;
final ScrollBehavior2 scrollBehavior;
final List<Widget> slivers; final List<Widget> slivers;
Axis get axis => axisDirectionToAxis(axisDirection); Axis get axis => axisDirectionToAxis(axisDirection);
...@@ -90,7 +87,6 @@ class TestScrollable extends StatelessWidget { ...@@ -90,7 +87,6 @@ class TestScrollable extends StatelessWidget {
return new Scrollable2( return new Scrollable2(
axisDirection: axisDirection, axisDirection: axisDirection,
physics: physics, physics: physics,
scrollBehavior: scrollBehavior,
viewportBuilder: (BuildContext context, ViewportOffset offset) { viewportBuilder: (BuildContext context, ViewportOffset offset) {
return new Viewport2( return new Viewport2(
axisDirection: axisDirection, axisDirection: axisDirection,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment