mirror of
https://github.com/flutter/flutter
synced 2024-08-25 02:46:31 +00:00
Introduce Split
curve (#143130)
`Split` is a curve that progresses according to `beginCurve` until `split`, then according to `endCurve`. This curve is used with bottom sheets to allow linear finger dragging and non-linear enter/exit animations. This PR cleans up a previously private class I introduced and replaces it with the more customisable `Split`. Fixes https://github.com/flutter/flutter/issues/51627 Diagram to be added with https://github.com/flutter/assets-for-api-docs/pull/239 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
This commit is contained in:
parent
988bee803c
commit
3109b1118e
|
@ -2,7 +2,6 @@
|
|||
// 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 'dart:ui';
|
||||
|
||||
|
@ -193,6 +192,79 @@ class Interval extends Curve {
|
|||
}
|
||||
}
|
||||
|
||||
/// A curve that progresses according to [beginCurve] until [split], then
|
||||
/// according to [endCurve].
|
||||
///
|
||||
/// Split curves are useful in situations where a widget must track the
|
||||
/// user's finger (which requires a linear animation), but can also be flung
|
||||
/// using a curve specified with the [endCurve] argument, after the finger is
|
||||
/// released. In such a case, the value of [split] would be the progress
|
||||
/// of the animation at the time when the finger was released.
|
||||
///
|
||||
/// For example, if [split] is set to 0.5, [beginCurve] is [Curves.linear],
|
||||
/// and [endCurve] is [Curves.easeOutCubic], then the bottom-left quarter of the
|
||||
/// curve will be a straight line, and the top-right quarter will contain the
|
||||
/// entire [Curves.easeOutCubic] curve.
|
||||
///
|
||||
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_split.mp4}
|
||||
class Split extends Curve {
|
||||
/// Creates a split curve.
|
||||
const Split(
|
||||
this.split, {
|
||||
this.beginCurve = Curves.linear,
|
||||
this.endCurve = Curves.easeOutCubic,
|
||||
});
|
||||
|
||||
/// The progress value separating [beginCurve] from [endCurve].
|
||||
///
|
||||
/// The value before which the curve progresses according to [beginCurve] and
|
||||
/// after which the curve progresses according to [endCurve].
|
||||
///
|
||||
/// When t is exactly `split`, the curve has the value `split`.
|
||||
///
|
||||
/// Must be between 0 and 1.0, inclusively.
|
||||
final double split;
|
||||
|
||||
/// The curve to use before [split] is reached.
|
||||
///
|
||||
/// Defaults to [Curves.linear].
|
||||
final Curve beginCurve;
|
||||
|
||||
/// The curve to use after [split] is reached.
|
||||
///
|
||||
/// Defaults to [Curves.easeOutCubic].
|
||||
final Curve endCurve;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
assert(t >= 0.0 && t <= 1.0);
|
||||
assert(split >= 0.0 && split <= 1.0);
|
||||
|
||||
if (t == 0.0 || t == 1.0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t == split) {
|
||||
return split;
|
||||
}
|
||||
|
||||
if (t < split) {
|
||||
final double curveProgress = t / split;
|
||||
final double transformed = beginCurve.transform(curveProgress);
|
||||
return lerpDouble(0, split, transformed)!;
|
||||
} else {
|
||||
final double curveProgress = (t - split) / (1 - split);
|
||||
final double transformed = endCurve.transform(curveProgress);
|
||||
return lerpDouble(split, 1, transformed)!;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${describeIdentity(this)}($split, $beginCurve, $endCurve)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0.
|
||||
///
|
||||
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.mp4}
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -705,9 +702,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
|||
|
||||
void handleDragEnd(DragEndDetails details, {bool? isClosing}) {
|
||||
// Allow the bottom sheet to animate smoothly from its current position.
|
||||
animationCurve = _BottomSheetSuspendedCurve(
|
||||
animationCurve = Split(
|
||||
widget.route.animation!.value,
|
||||
curve: _modalBottomSheetCurve,
|
||||
endCurve: _modalBottomSheetCurve,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1124,61 +1121,6 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(guidezpl): Look into making this public. A copy of this class is in
|
||||
// scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
|
||||
/// A curve that progresses linearly until a specified [startingPoint], at which
|
||||
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
|
||||
/// but will use [startingPoint] as the Y position.
|
||||
///
|
||||
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
|
||||
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
|
||||
/// straight line, and the top-right quarter will contain the entire contents of
|
||||
/// [Curves.easeOut].
|
||||
///
|
||||
/// This is useful in situations where a widget must track the user's finger
|
||||
/// (which requires a linear animation), and afterwards can be flung using a
|
||||
/// curve specified with the [curve] argument, after the finger is released. In
|
||||
/// such a case, the value of [startingPoint] would be the progress of the
|
||||
/// animation at the time when the finger was released.
|
||||
class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
|
||||
/// Creates a suspended curve.
|
||||
const _BottomSheetSuspendedCurve(
|
||||
this.startingPoint, {
|
||||
this.curve = Curves.easeOutCubic,
|
||||
});
|
||||
|
||||
/// The progress value at which [curve] should begin.
|
||||
final double startingPoint;
|
||||
|
||||
/// The curve to use when [startingPoint] is reached.
|
||||
///
|
||||
/// This defaults to [Curves.easeOutCubic].
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
assert(t >= 0.0 && t <= 1.0);
|
||||
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
|
||||
|
||||
if (t < startingPoint) {
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t == 1.0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
final double curveProgress = (t - startingPoint) / (1 - startingPoint);
|
||||
final double transformed = curve.transform(curveProgress);
|
||||
return lerpDouble(startingPoint, 1, transformed)!;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${describeIdentity(this)}($startingPoint, $curve)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows a modal Material Design bottom sheet.
|
||||
///
|
||||
/// {@macro flutter.material.ModalBottomSheetRoute}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||
|
@ -3119,61 +3118,6 @@ class ScaffoldFeatureController<T extends Widget, U> {
|
|||
final StateSetter? setState;
|
||||
}
|
||||
|
||||
// TODO(guidezpl): Look into making this public. A copy of this class is in
|
||||
// bottom_sheet.dart, for now, https://github.com/flutter/flutter/issues/51627
|
||||
/// A curve that progresses linearly until a specified [startingPoint], at which
|
||||
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
|
||||
/// but will use [startingPoint] as the Y position.
|
||||
///
|
||||
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
|
||||
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
|
||||
/// straight line, and the top-right quarter will contain the entire contents of
|
||||
/// [Curves.easeOut].
|
||||
///
|
||||
/// This is useful in situations where a widget must track the user's finger
|
||||
/// (which requires a linear animation), and afterwards can be flung using a
|
||||
/// curve specified with the [curve] argument, after the finger is released. In
|
||||
/// such a case, the value of [startingPoint] would be the progress of the
|
||||
/// animation at the time when the finger was released.
|
||||
class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
|
||||
/// Creates a suspended curve.
|
||||
const _BottomSheetSuspendedCurve(
|
||||
this.startingPoint, {
|
||||
this.curve = Curves.easeOutCubic,
|
||||
});
|
||||
|
||||
/// The progress value at which [curve] should begin.
|
||||
///
|
||||
/// This defaults to [Curves.easeOutCubic].
|
||||
final double startingPoint;
|
||||
|
||||
/// The curve to use when [startingPoint] is reached.
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
assert(t >= 0.0 && t <= 1.0);
|
||||
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
|
||||
|
||||
if (t < startingPoint) {
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t == 1.0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
final double curveProgress = (t - startingPoint) / (1 - startingPoint);
|
||||
final double transformed = curve.transform(curveProgress);
|
||||
return lerpDouble(startingPoint, 1, transformed)!;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${describeIdentity(this)}($startingPoint, $curve)';
|
||||
}
|
||||
}
|
||||
|
||||
class _StandardBottomSheet extends StatefulWidget {
|
||||
const _StandardBottomSheet({
|
||||
super.key,
|
||||
|
@ -3247,9 +3191,9 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
|||
|
||||
void _handleDragEnd(DragEndDetails details, { bool? isClosing }) {
|
||||
// Allow the bottom sheet to animate smoothly from its current position.
|
||||
animationCurve = _BottomSheetSuspendedCurve(
|
||||
animationCurve = Split(
|
||||
widget.animationController.value,
|
||||
curve: _standardBottomSheetCurve,
|
||||
endCurve: _standardBottomSheetCurve,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ void main() {
|
|||
expect(const SawTooth(3), hasOneLineDescription);
|
||||
expect(const Interval(0.25, 0.75), hasOneLineDescription);
|
||||
expect(const Interval(0.25, 0.75, curve: Curves.ease), hasOneLineDescription);
|
||||
expect(const Split(0.25, beginCurve: Curves.ease), hasOneLineDescription);
|
||||
});
|
||||
|
||||
test('Curve flipped control test', () {
|
||||
|
@ -187,6 +188,9 @@ void main() {
|
|||
expect(() => const Interval(0.0, 1.0).transform(-0.0001), throwsAssertionError);
|
||||
expect(() => const Interval(0.0, 1.0).transform(1.0001), throwsAssertionError);
|
||||
|
||||
expect(() => const Split(0.0).transform(-0.0001), throwsAssertionError);
|
||||
expect(() => const Split(0.0).transform(1.0001), throwsAssertionError);
|
||||
|
||||
expect(() => const Threshold(0.5).transform(-0.0001), throwsAssertionError);
|
||||
expect(() => const Threshold(0.5).transform(1.0001), throwsAssertionError);
|
||||
|
||||
|
@ -222,6 +226,9 @@ void main() {
|
|||
expect(const Interval(0, 1).transform(0), 0);
|
||||
expect(const Interval(0, 1).transform(1), 1);
|
||||
|
||||
expect(const Split(0.5).transform(0), 0);
|
||||
expect(const Split(0.5).transform(1), 1);
|
||||
|
||||
expect(const Threshold(0.5).transform(0), 0);
|
||||
expect(const Threshold(0.5).transform(1), 1);
|
||||
|
||||
|
@ -259,6 +266,19 @@ void main() {
|
|||
expect(Curves.bounceInOut.transform(1), 1);
|
||||
});
|
||||
|
||||
test('Split interpolates values properly', () {
|
||||
const Split curve = Split(0.3);
|
||||
|
||||
const double tolerance = 1e-6;
|
||||
expect(curve.transform(0.0), equals(0.0));
|
||||
expect(curve.transform(0.1), equals(0.1));
|
||||
expect(curve.transform(0.25), equals(0.25));
|
||||
expect(curve.transform(0.3), equals(0.3));
|
||||
expect(curve.transform(0.5), moreOrLessEquals(0.760461, epsilon: tolerance));
|
||||
expect(curve.transform(0.75), moreOrLessEquals(0.962055, epsilon: tolerance));
|
||||
expect(curve.transform(1.0), equals(1.0));
|
||||
});
|
||||
|
||||
test('CatmullRomSpline interpolates values properly', () {
|
||||
final CatmullRomSpline curve = CatmullRomSpline(
|
||||
const <Offset>[
|
||||
|
|
Loading…
Reference in a new issue