Make CircularProgressIndicator's animation match native (#50412)

This commit is contained in:
Coin 2020-05-17 05:47:03 +08:00 committed by GitHub
parent 32547dcc7e
commit e5bd2b3d55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 33 deletions

View file

@ -12,6 +12,7 @@ import 'theme.dart';
const double _kMinCircularProgressIndicatorSize = 36.0;
const int _kIndeterminateLinearDuration = 1800;
const int _kIndeterminateCircularDuration = 1333 * 2222;
/// A base class for material design progress indicators.
///
@ -331,12 +332,12 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
this.value,
this.headValue,
this.tailValue,
this.stepValue,
this.offsetValue,
this.rotationValue,
this.strokeWidth,
}) : arcStart = value != null
? _startAngle
: _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 1.7 - stepValue * 0.8 * math.pi,
: _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi,
arcSweep = value != null
? (value.clamp(0.0, 1.0) as double) * _sweep
: math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon);
@ -346,7 +347,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
final double value;
final double headValue;
final double tailValue;
final int stepValue;
final double offsetValue;
final double rotationValue;
final double strokeWidth;
final double arcStart;
@ -385,7 +386,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter {
|| oldPainter.value != value
|| oldPainter.headValue != headValue
|| oldPainter.tailValue != tailValue
|| oldPainter.stepValue != stepValue
|| oldPainter.offsetValue != offsetValue
|| oldPainter.rotationValue != rotationValue
|| oldPainter.strokeWidth != strokeWidth;
}
@ -443,31 +444,30 @@ class CircularProgressIndicator extends ProgressIndicator {
_CircularProgressIndicatorState createState() => _CircularProgressIndicatorState();
}
// Tweens used by circular progress indicator
final Animatable<double> _kStrokeHeadTween = CurveTween(
curve: const Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
).chain(CurveTween(
curve: const SawTooth(5),
));
final Animatable<double> _kStrokeTailTween = CurveTween(
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
).chain(CurveTween(
curve: const SawTooth(5),
));
final Animatable<int> _kStepTween = StepTween(begin: 0, end: 5);
final Animatable<double> _kRotationTween = CurveTween(curve: const SawTooth(5));
class _CircularProgressIndicatorState extends State<CircularProgressIndicator> with SingleTickerProviderStateMixin {
static const int _pathCount = _kIndeterminateCircularDuration ~/ 1333;
static const int _rotationCount = _kIndeterminateCircularDuration ~/ 2222;
static final Animatable<double> _strokeHeadTween = CurveTween(
curve: const Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
).chain(CurveTween(
curve: const SawTooth(_pathCount),
));
static final Animatable<double> _strokeTailTween = CurveTween(
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
).chain(CurveTween(
curve: const SawTooth(_pathCount),
));
static final Animatable<double> _offsetTween = CurveTween(curve: const SawTooth(_pathCount));
static final Animatable<double> _rotationTween = CurveTween(curve: const SawTooth(_rotationCount));
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 5),
duration: const Duration(milliseconds: _kIndeterminateCircularDuration),
vsync: this,
);
if (widget.value == null)
@ -489,7 +489,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
super.dispose();
}
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, int stepValue, double rotationValue) {
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
return widget._buildSemanticsWrapper(
context: context,
child: Container(
@ -504,7 +504,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
value: widget.value, // may be null
headValue: headValue, // remaining arguments are ignored if widget.value is not null
tailValue: tailValue,
stepValue: stepValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
),
@ -519,10 +519,10 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
builder: (BuildContext context, Widget child) {
return _buildIndicator(
context,
_kStrokeHeadTween.evaluate(_controller),
_kStrokeTailTween.evaluate(_controller),
_kStepTween.evaluate(_controller),
_kRotationTween.evaluate(_controller),
_strokeHeadTween.evaluate(_controller),
_strokeTailTween.evaluate(_controller),
_offsetTween.evaluate(_controller),
_rotationTween.evaluate(_controller),
);
},
);
@ -542,7 +542,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
double value,
double headValue,
double tailValue,
int stepValue,
double offsetValue,
double rotationValue,
double strokeWidth,
this.arrowheadScale,
@ -551,7 +551,7 @@ class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter
value: value,
headValue: headValue,
tailValue: tailValue,
stepValue: stepValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: strokeWidth,
);
@ -645,14 +645,14 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
@override
Widget build(BuildContext context) {
if (widget.value != null)
_controller.value = widget.value / 10.0;
_controller.value = widget.value * (1333 / 2 / _kIndeterminateCircularDuration);
else if (!_controller.isAnimating)
_controller.repeat();
return _buildAnimation();
}
@override
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, int stepValue, double rotationValue) {
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
final double arrowheadScale = widget.value == null ? 0.0 : ((widget.value * 2.0).clamp(0.0, 1.0) as double);
return widget._buildSemanticsWrapper(
context: context,
@ -672,7 +672,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
value: null, // Draw the indeterminate progress indicator.
headValue: headValue,
tailValue: tailValue,
stepValue: stepValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
arrowheadScale: arrowheadScale,

View file

@ -530,4 +530,28 @@ void main() {
handle.dispose();
});
testWidgets('Indeterminate CircularProgressIndicator uses expected animation', (WidgetTester tester) async {
final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(40, 40));
await tester.pumpFrames(animationSheet.record(
const Directionality(
textDirection: TextDirection.ltr,
child: Padding(
padding: EdgeInsets.all(4),
child: CircularProgressIndicator(),
),
),
), const Duration(seconds: 2));
tester.binding.setSurfaceSize(animationSheet.sheetSize());
final Widget display = await animationSheet.display();
await tester.pumpWidget(display);
await expectLater(
find.byWidget(display),
matchesGoldenFile('material.circular_progress_indicator.indeterminate.png'),
);
}, skip: isBrowser);
}