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 {
bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}
class _MaterialScrollBehavior extends ViewportScrollBehavior {
class _MaterialScrollBehavior extends ScrollBehavior2 {
@override
TargetPlatform getPlatform(BuildContext context) {
return Theme.of(context).platform;
......@@ -308,7 +308,7 @@ class _MaterialAppState extends State<MaterialApp> {
);
return new ScrollConfiguration2(
delegate: new _MaterialScrollBehavior(),
behavior: new _MaterialScrollBehavior(),
child: result
);
}
......
......@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:async' show Timer;
import 'dart:math' as math;
......
......@@ -180,8 +180,9 @@ class _ScrollbarPainter extends CustomPainter {
}
}
////////////////////////////////////////////////////////////////////////////////
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
const double _kMinScrollbarThumbExtent = 18.0;
const double _kScrollbarThumbGirth = 6.0;
......
......@@ -30,6 +30,8 @@ import 'tolerance.dart';
/// should establish a convention and use that convention consistently with all
/// related objects.
abstract class Simulation {
Simulation({ this.tolerance: Tolerance.defaultTolerance });
/// The position of the object in the simulation at the given time.
double x(double time);
......@@ -46,7 +48,7 @@ abstract class Simulation {
/// 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
/// tolerance defines how to determine if the difference could not be seen.
Tolerance tolerance = Tolerance.defaultTolerance;
Tolerance tolerance;
@override
String toString() => '$runtimeType';
......
......@@ -5,6 +5,7 @@
import 'dart:math' as math;
import 'simulation.dart';
import 'tolerance.dart';
import 'utils.dart';
/// Structure that describes a spring's constants.
......@@ -86,12 +87,14 @@ class SpringSimulation extends Simulation {
/// arbitrary unit of length, and T is the time unit used for driving the
/// [SpringSimulation].
SpringSimulation(
SpringDescription desc,
SpringDescription spring,
double start,
double end,
double velocity
) : _endPosition = end,
_solution = new _SpringSolution(desc, start - end, velocity);
double velocity, {
Tolerance tolerance: Tolerance.defaultTolerance,
}) : _endPosition = end,
_solution = new _SpringSolution(spring, start - end, velocity),
super(tolerance: tolerance);
final double _endPosition;
final _SpringSolution _solution;
......@@ -124,11 +127,12 @@ class ScrollSpringSimulation extends SpringSimulation {
/// See the [new SpringSimulation] constructor on the superclass for a
/// discussion of the arguments' units.
ScrollSpringSimulation(
SpringDescription desc,
SpringDescription spring,
double start,
double end,
double velocity
) : super(desc, start, end, velocity);
double velocity, {
Tolerance tolerance: Tolerance.defaultTolerance,
}) : super(spring, start, end, velocity, tolerance: tolerance);
@override
double x(double time) => isDone(time) ? _endPosition : super.x(time);
......@@ -139,22 +143,22 @@ class ScrollSpringSimulation extends SpringSimulation {
abstract class _SpringSolution {
factory _SpringSolution(
SpringDescription desc,
SpringDescription spring,
double initialPosition,
double initialVelocity
) {
assert(desc != null);
assert(desc.mass != null);
assert(desc.springConstant != null);
assert(desc.damping != null);
assert(spring != null);
assert(spring.mass != null);
assert(spring.springConstant != null);
assert(spring.damping != null);
assert(initialPosition != 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)
return new _CriticalSolution(desc, initialPosition, initialVelocity);
return new _CriticalSolution(spring, initialPosition, initialVelocity);
if (cmk > 0.0)
return new _OverdampedSolution(desc, initialPosition, initialVelocity);
return new _UnderdampedSolution(desc, initialPosition, initialVelocity);
return new _OverdampedSolution(spring, initialPosition, initialVelocity);
return new _UnderdampedSolution(spring, initialPosition, initialVelocity);
}
double x(double time);
......@@ -164,11 +168,11 @@ abstract class _SpringSolution {
class _CriticalSolution implements _SpringSolution {
factory _CriticalSolution(
SpringDescription desc,
SpringDescription spring,
double distance,
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 c2 = velocity / (r * distance);
return new _CriticalSolution.withArgs(r, c1, c2);
......@@ -198,13 +202,13 @@ class _CriticalSolution implements _SpringSolution {
class _OverdampedSolution implements _SpringSolution {
factory _OverdampedSolution(
SpringDescription desc,
SpringDescription spring,
double distance,
double velocity
) {
final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant;
final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass);
final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass);
final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.springConstant;
final double r1 = (-spring.damping - math.sqrt(cmk)) / (2.0 * spring.mass);
final double r2 = (-spring.damping + math.sqrt(cmk)) / (2.0 * spring.mass);
final double c2 = (velocity - r1 * distance) / (r2 - r1);
final double c1 = distance - c2;
return new _OverdampedSolution.withArgs(r1, r2, c1, c2);
......@@ -236,13 +240,13 @@ class _OverdampedSolution implements _SpringSolution {
class _UnderdampedSolution implements _SpringSolution {
factory _UnderdampedSolution(
SpringDescription desc,
SpringDescription spring,
double distance,
double velocity
) {
final double w = math.sqrt(4.0 * desc.mass * desc.springConstant -
desc.damping * desc.damping) / (2.0 * desc.mass);
final double r = -(desc.damping / 2.0 * desc.mass);
final double w = math.sqrt(4.0 * spring.mass * spring.springConstant -
spring.damping * spring.damping) / (2.0 * spring.mass);
final double r = -(spring.damping / 2.0 * spring.mass);
final double c1 = distance;
final double c2 = (velocity - r * distance) / w;
return new _UnderdampedSolution.withArgs(w, r, c1, c2);
......
......@@ -62,8 +62,8 @@ class GlowingOverscrollIndicator extends StatefulWidget {
/// widget.
///
/// Typically a [GlowingOverscrollIndicator] is created by a
/// [ScrollBehavior2.wrap] method, in which case the child is usually the one
/// provided as an argument to that method.
/// [ScrollBehavior2.buildViewportChrome] method, in which case
/// the child is usually the one provided as an argument to that method.
final Widget child;
@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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math;
......
......@@ -2,12 +2,86 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.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.
///
......
// 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 {
@required this.position,
@required this.velocity,
this.friction: 0.015,
}) {
Tolerance tolerance: Tolerance.defaultTolerance,
}) : super(tolerance: tolerance) {
_scaledFriction = friction * _decelerationForFriction(0.84); // See mPhysicalCoeff
_duration = _flingDuration(velocity);
_distance = _flingDistance(velocity);
......@@ -196,7 +197,9 @@ class ClampingScrollSimulation extends Simulation {
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 double _kDrag = 0.025;
......
......@@ -7,8 +7,8 @@ import 'package:meta/meta.dart';
import 'framework.dart';
import 'basic.dart';
import 'page_scroll_physics.dart';
import 'scroll_absolute.dart';
import 'scroll_physics.dart';
import 'scroll_position.dart';
import 'scrollable.dart';
import 'sliver.dart';
import 'viewport.dart';
......
......@@ -46,10 +46,11 @@ export 'src/widgets/performance_overlay.dart';
export 'src/widgets/placeholder.dart';
export 'src/widgets/raw_keyboard_listener.dart';
export 'src/widgets/routes.dart';
export 'src/widgets/scroll_absolute.dart';
export 'src/widgets/scroll_behavior.dart';
export 'src/widgets/scroll_configuration.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_view.dart';
export 'src/widgets/scrollable.dart';
......
......@@ -72,7 +72,7 @@ void main() {
log.clear();
Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position;
ScrollPosition position = state.position;
position.jumpTo(2025.0);
expect(log, isEmpty);
......
......@@ -204,12 +204,14 @@ void main() {
RenderObject painter;
await tester.pumpWidget(
new TestScrollable(
axisDirection: AxisDirection.left,
scrollBehavior: new TestScrollBehavior1(),
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
new ScrollConfiguration2(
behavior: new TestScrollBehavior1(),
child: new TestScrollable(
axisDirection: AxisDirection.left,
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
),
),
);
painter = tester.renderObject(find.byType(CustomPaint));
......@@ -219,12 +221,14 @@ void main() {
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
await tester.pumpWidget(
new TestScrollable(
axisDirection: AxisDirection.right,
scrollBehavior: new TestScrollBehavior2(),
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
new ScrollConfiguration2(
behavior: new TestScrollBehavior2(),
child: new TestScrollable(
axisDirection: AxisDirection.right,
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 20.0)),
],
),
),
);
painter = tester.renderObject(find.byType(CustomPaint));
......@@ -234,14 +238,14 @@ void main() {
});
}
class TestScrollBehavior1 extends ViewportScrollBehavior {
class TestScrollBehavior1 extends ScrollBehavior2 {
@override
Color getGlowColor(BuildContext context) {
return const Color(0xFF00FF00);
}
}
class TestScrollBehavior2 extends ViewportScrollBehavior {
class TestScrollBehavior2 extends ScrollBehavior2 {
@override
Color getGlowColor(BuildContext context) {
return const Color(0xFF0000FF);
......
......@@ -158,7 +158,7 @@ void main() {
Scrollable2State state = tester.state(find.byType(Scrollable2));
AbsoluteScrollPosition position = state.position;
ScrollPosition position = state.position;
position.jumpTo(3025.0);
expect(log, isEmpty);
......
......@@ -10,13 +10,13 @@ import 'test_widgets.dart';
class TestScrollPosition extends ScrollPosition {
TestScrollPosition(
this.extentMultiplier,
Scrollable2State state,
Tolerance scrollTolerances,
TestScrollPhysics physics,
AbstractScrollState state,
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;
......@@ -27,7 +27,7 @@ class TestScrollPosition extends ScrollPosition {
double setPixels(double value) {
double oldPixels = _pixels;
_pixels = value;
dispatchNotification(activity.createScrollUpdateNotification(state, _pixels - oldPixels));
state.dispatchNotification(activity.createScrollUpdateNotification(state, _pixels - oldPixels));
return 0.0;
}
......@@ -56,9 +56,9 @@ class TestScrollPosition extends ScrollPosition {
double afterExtent = _max - _pixels;
if (insideExtent > 0.0) {
return new ScrollableMetrics(
extentBefore: extentMultiplier * beforeExtent / insideExtent,
extentInside: extentMultiplier,
extentAfter: extentMultiplier * afterExtent / insideExtent,
extentBefore: physics.extentMultiplier * beforeExtent / insideExtent,
extentInside: physics.extentMultiplier,
extentAfter: physics.extentMultiplier * afterExtent / insideExtent,
);
} else {
return new ScrollableMetrics(
......@@ -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 {
TestScrollBehavior(this.extentMultiplier);
final double extentMultiplier;
@override
Widget wrap(BuildContext context, Widget child, AxisDirection axisDirection) => child;
ScrollPhysics getScrollPhysics(BuildContext context) {
return new TestScrollPhysics(
extentMultiplier: extentMultiplier
).applyTo(super.getScrollPhysics(context));
}
@override
ScrollPosition createScrollPosition(BuildContext context, Scrollable2State state, ScrollPosition oldPosition, ScrollPhysics physics) {
return new TestScrollPosition(extentMultiplier, state, ViewportScrollBehavior.defaultScrollTolerances, oldPosition);
}
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) => child;
@override
bool shouldNotify(TestScrollBehavior oldDelegate) {
......@@ -90,20 +109,24 @@ class TestScrollBehavior extends ScrollBehavior2 {
void main() {
testWidgets('Changing the scroll behavior dynamically', (WidgetTester tester) async {
await tester.pumpWidget(new TestScrollable(
scrollBehavior: new TestScrollBehavior(1.0),
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
await tester.pumpWidget(new ScrollConfiguration2(
behavior: new TestScrollBehavior(1.0),
child: new TestScrollable(
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
),
));
Scrollable2State state = tester.state(find.byType(Scrollable2));
expect(state.position.getMetrics().extentInside, 1.0);
await tester.pumpWidget(new TestScrollable(
scrollBehavior: new TestScrollBehavior(2.0),
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
await tester.pumpWidget(new ScrollConfiguration2(
behavior: new TestScrollBehavior(2.0),
child: new TestScrollable(
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 2000.0)),
],
),
));
expect(state.position.getMetrics().extentInside, 2.0);
});
......
......@@ -32,7 +32,7 @@ double getScrollOffset(WidgetTester tester) {
void resetScrollOffset(WidgetTester tester) {
RenderViewport2 viewport = tester.renderObject(find.byType(Viewport2));
AbsoluteScrollPosition position = viewport.offset;
ScrollPosition position = viewport.offset;
position.jumpTo(0.0);
}
......
......@@ -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
assert(max < 10000.0);
expect(max, 1600.0);
......@@ -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(key2, new Offset(0.0, 600.0), false);
......@@ -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(key2, new Offset(0.0, 600.0), false);
......@@ -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(key2, new Offset(0.0, 600.0), false);
......
......@@ -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
assert(max < 10000.0);
expect(max, 1450.0);
......@@ -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(key2, new Offset(0.0, 550.0), true);
verifyPaintPosition(key3, new Offset(0.0, 600.0), false);
......@@ -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
assert(max < 10000.0);
expect(max, 1750.0);
......
......@@ -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
assert(max < 10000.0);
expect(max, 1450.0);
......@@ -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));
await tester.pumpUntilNoTransientCallbacks(const Duration(milliseconds: 1000));
RenderBox box = tester.renderObject<RenderBox>(find.byType(Container));
......
......@@ -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
assert(max < 10000.0);
expect(max, 1450.0);
......
......@@ -65,7 +65,6 @@ class TestScrollable extends StatelessWidget {
this.physics,
this.anchor: 0.0,
this.center,
this.scrollBehavior,
this.slivers: const <Widget>[],
}) {
assert(slivers != null);
......@@ -79,8 +78,6 @@ class TestScrollable extends StatelessWidget {
final Key center;
final ScrollBehavior2 scrollBehavior;
final List<Widget> slivers;
Axis get axis => axisDirectionToAxis(axisDirection);
......@@ -90,7 +87,6 @@ class TestScrollable extends StatelessWidget {
return new Scrollable2(
axisDirection: axisDirection,
physics: physics,
scrollBehavior: scrollBehavior,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return new Viewport2(
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