mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Implement new activity indicator for iOS14 (#60179)
Updates the activity indicator style to iOS14, but places it behind a flag, to be deprecated when iOS 14 is released.
This commit is contained in:
parent
ce55e42f2b
commit
c047769487
|
@ -18,6 +18,15 @@ const Color _kActiveTickColor = CupertinoDynamicColor.withBrightness(
|
||||||
darkColor: Color(0xFFEBEBF5),
|
darkColor: Color(0xFFEBEBF5),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Define the iOS version style of [CupertinoActivityIndicator].
|
||||||
|
enum CupertinoActivityIndicatorIOSVersionStyle {
|
||||||
|
/// The style that is used in iOS13 and earlier (12 points).
|
||||||
|
iOS13,
|
||||||
|
|
||||||
|
/// The style that was introduced in iOS14 (8 points).
|
||||||
|
iOS14,
|
||||||
|
}
|
||||||
|
|
||||||
/// An iOS-style activity indicator that spins clockwise.
|
/// An iOS-style activity indicator that spins clockwise.
|
||||||
///
|
///
|
||||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=AENVH-ZqKDQ}
|
/// {@youtube 560 315 https://www.youtube.com/watch?v=AENVH-ZqKDQ}
|
||||||
|
@ -31,6 +40,7 @@ class CupertinoActivityIndicator extends StatefulWidget {
|
||||||
Key key,
|
Key key,
|
||||||
this.animating = true,
|
this.animating = true,
|
||||||
this.radius = _kDefaultIndicatorRadius,
|
this.radius = _kDefaultIndicatorRadius,
|
||||||
|
this.iOSVersionStyle = CupertinoActivityIndicatorIOSVersionStyle.iOS13,
|
||||||
}) : assert(animating != null),
|
}) : assert(animating != null),
|
||||||
assert(radius != null),
|
assert(radius != null),
|
||||||
assert(radius > 0.0),
|
assert(radius > 0.0),
|
||||||
|
@ -47,6 +57,7 @@ class CupertinoActivityIndicator extends StatefulWidget {
|
||||||
Key key,
|
Key key,
|
||||||
this.radius = _kDefaultIndicatorRadius,
|
this.radius = _kDefaultIndicatorRadius,
|
||||||
this.progress = 1.0,
|
this.progress = 1.0,
|
||||||
|
this.iOSVersionStyle = CupertinoActivityIndicatorIOSVersionStyle.iOS13,
|
||||||
}) : assert(radius != null),
|
}) : assert(radius != null),
|
||||||
assert(radius > 0.0),
|
assert(radius > 0.0),
|
||||||
assert(progress != null),
|
assert(progress != null),
|
||||||
|
@ -73,12 +84,19 @@ class CupertinoActivityIndicator extends StatefulWidget {
|
||||||
/// Defaults to 1.0. Must be between 0.0 and 1.0 inclusive, and cannot be null.
|
/// Defaults to 1.0. Must be between 0.0 and 1.0 inclusive, and cannot be null.
|
||||||
final double progress;
|
final double progress;
|
||||||
|
|
||||||
|
/// The iOS version style of activity indicator.
|
||||||
|
///
|
||||||
|
/// Defaults to [CupertinoActivityIndicatorIOSVersionStyle.iOS13].
|
||||||
|
// TODO(ctrysbita): Change default style to iOS14 after official release, https://github.com/flutter/flutter/issues/60047
|
||||||
|
final CupertinoActivityIndicatorIOSVersionStyle iOSVersionStyle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CupertinoActivityIndicatorState createState() => _CupertinoActivityIndicatorState();
|
_CupertinoActivityIndicatorState createState() =>
|
||||||
|
_CupertinoActivityIndicatorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
|
||||||
class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator> with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
AnimationController _controller;
|
AnimationController _controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -89,9 +107,10 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.animating)
|
if (widget.animating) {
|
||||||
_controller.repeat();
|
_controller.repeat();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(CupertinoActivityIndicator oldWidget) {
|
void didUpdateWidget(CupertinoActivityIndicator oldWidget) {
|
||||||
|
@ -118,9 +137,11 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: _CupertinoActivityIndicatorPainter(
|
painter: _CupertinoActivityIndicatorPainter(
|
||||||
position: _controller,
|
position: _controller,
|
||||||
activeColor: CupertinoDynamicColor.resolve(_kActiveTickColor, context),
|
activeColor:
|
||||||
|
CupertinoDynamicColor.resolve(_kActiveTickColor, context),
|
||||||
radius: widget.radius,
|
radius: widget.radius,
|
||||||
progress: widget.progress,
|
progress: widget.progress,
|
||||||
|
iOSVersionStyle: widget.iOSVersionStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -128,15 +149,42 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
|
||||||
}
|
}
|
||||||
|
|
||||||
const double _kTwoPI = math.pi * 2.0;
|
const double _kTwoPI = math.pi * 2.0;
|
||||||
const int _kTickCount = 12;
|
|
||||||
|
|
||||||
// Alpha values extracted from the native component (for both dark and light mode) to
|
/// Alpha values extracted from the native component (for both dark and light mode) to
|
||||||
// draw the spinning ticks. The list must have a length of _kTickCount. The order of
|
/// draw the spinning ticks.
|
||||||
// these values is designed to match the first frame of the iOS activity indicator which
|
const Map<CupertinoActivityIndicatorIOSVersionStyle, List<int>>
|
||||||
// has the most prominent tick at 9 o'clock.
|
_kAlphaValuesMap = <CupertinoActivityIndicatorIOSVersionStyle, List<int>>{
|
||||||
const List<int> _alphaValues = <int>[47, 47, 47, 47, 64, 81, 97, 114, 131, 147, 47, 47];
|
/// The order of these values is designed to match the first frame of the iOS activity indicator which
|
||||||
|
/// has the most prominent tick at 9 o'clock.
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS13: <int>[
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
64,
|
||||||
|
81,
|
||||||
|
97,
|
||||||
|
114,
|
||||||
|
131,
|
||||||
|
147,
|
||||||
|
47,
|
||||||
|
47
|
||||||
|
],
|
||||||
|
|
||||||
// The alpha value that is used to draw the partially revealed ticks.
|
/// Alpha values for new style that introduced in iOS14.
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS14: <int>[
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
47,
|
||||||
|
72,
|
||||||
|
97,
|
||||||
|
122,
|
||||||
|
147,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The alpha value that is used to draw the partially revealed ticks.
|
||||||
const int _partiallyRevealedAlpha = 147;
|
const int _partiallyRevealedAlpha = 147;
|
||||||
|
|
||||||
class _CupertinoActivityIndicatorPainter extends CustomPainter {
|
class _CupertinoActivityIndicatorPainter extends CustomPainter {
|
||||||
|
@ -145,9 +193,16 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
|
||||||
@required this.activeColor,
|
@required this.activeColor,
|
||||||
@required this.radius,
|
@required this.radius,
|
||||||
@required this.progress,
|
@required this.progress,
|
||||||
}) : tickFundamentalRRect = RRect.fromLTRBXY(
|
CupertinoActivityIndicatorIOSVersionStyle iOSVersionStyle =
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS13,
|
||||||
|
}) : alphaValues = _kAlphaValuesMap[iOSVersionStyle],
|
||||||
|
tickFundamentalRRect = RRect.fromLTRBXY(
|
||||||
-radius / _kDefaultIndicatorRadius,
|
-radius / _kDefaultIndicatorRadius,
|
||||||
-radius / 2.0,
|
-radius /
|
||||||
|
(iOSVersionStyle ==
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS14
|
||||||
|
? 3.0
|
||||||
|
: 2.0),
|
||||||
radius / _kDefaultIndicatorRadius,
|
radius / _kDefaultIndicatorRadius,
|
||||||
-radius,
|
-radius,
|
||||||
radius / _kDefaultIndicatorRadius,
|
radius / _kDefaultIndicatorRadius,
|
||||||
|
@ -156,25 +211,29 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
|
||||||
super(repaint: position);
|
super(repaint: position);
|
||||||
|
|
||||||
final Animation<double> position;
|
final Animation<double> position;
|
||||||
final RRect tickFundamentalRRect;
|
|
||||||
final Color activeColor;
|
final Color activeColor;
|
||||||
final double radius;
|
final double radius;
|
||||||
final double progress;
|
final double progress;
|
||||||
|
|
||||||
|
final List<int> alphaValues;
|
||||||
|
final RRect tickFundamentalRRect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final Paint paint = Paint();
|
final Paint paint = Paint();
|
||||||
|
final int tickCount = alphaValues.length;
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(size.width / 2.0, size.height / 2.0);
|
canvas.translate(size.width / 2.0, size.height / 2.0);
|
||||||
|
|
||||||
final int activeTick = (_kTickCount * position.value).floor();
|
final int activeTick = (tickCount * position.value).floor();
|
||||||
|
|
||||||
for (int i = 0; i < _kTickCount * progress; ++i) {
|
for (int i = 0; i < tickCount * progress; ++i) {
|
||||||
final int t = (i - activeTick) % _kTickCount;
|
final int t = (i - activeTick) % tickCount;
|
||||||
paint.color = activeColor.withAlpha(progress < 1 ? _partiallyRevealedAlpha : _alphaValues[t]);
|
paint.color = activeColor
|
||||||
|
.withAlpha(progress < 1 ? _partiallyRevealedAlpha : alphaValues[t]);
|
||||||
canvas.drawRRect(tickFundamentalRRect, paint);
|
canvas.drawRRect(tickFundamentalRRect, paint);
|
||||||
canvas.rotate(_kTwoPI / _kTickCount);
|
canvas.rotate(_kTwoPI / tickCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
@ -182,6 +241,8 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
|
bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
|
||||||
return oldPainter.position != position || oldPainter.activeColor != activeColor || oldPainter.progress != progress;
|
return oldPainter.position != position ||
|
||||||
|
oldPainter.activeColor != activeColor ||
|
||||||
|
oldPainter.progress != progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../rendering/mock_canvas.dart';
|
import '../rendering/mock_canvas.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Activity indicator animate property works', (WidgetTester tester) async {
|
testWidgets('Activity indicator animate property works',
|
||||||
|
(WidgetTester tester) async {
|
||||||
await tester.pumpWidget(buildCupertinoActivityIndicator());
|
await tester.pumpWidget(buildCupertinoActivityIndicator());
|
||||||
expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
|
||||||
|
|
||||||
|
@ -38,7 +38,10 @@ void main() {
|
||||||
key: key,
|
key: key,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: CupertinoColors.white,
|
color: CupertinoColors.white,
|
||||||
child: const CupertinoActivityIndicator(animating: false, radius: 35),
|
child: const CupertinoActivityIndicator(
|
||||||
|
animating: false,
|
||||||
|
radius: 35,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -58,7 +61,10 @@ void main() {
|
||||||
key: key,
|
key: key,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: CupertinoColors.black,
|
color: CupertinoColors.black,
|
||||||
child: const CupertinoActivityIndicator(animating: false, radius: 35),
|
child: const CupertinoActivityIndicator(
|
||||||
|
animating: false,
|
||||||
|
radius: 35,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -71,6 +77,60 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Activity indicator with iOS14 style',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: MediaQuery(
|
||||||
|
data: const MediaQueryData(platformBrightness: Brightness.light),
|
||||||
|
child: RepaintBoundary(
|
||||||
|
key: key,
|
||||||
|
child: Container(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
child: const CupertinoActivityIndicator(
|
||||||
|
animating: false,
|
||||||
|
radius: 35,
|
||||||
|
iOSVersionStyle:
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
find.byKey(key),
|
||||||
|
matchesGoldenFile('activityIndicator.iOS14.paused.light.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Center(
|
||||||
|
child: MediaQuery(
|
||||||
|
data: const MediaQueryData(platformBrightness: Brightness.dark),
|
||||||
|
child: RepaintBoundary(
|
||||||
|
key: key,
|
||||||
|
child: Container(
|
||||||
|
color: CupertinoColors.black,
|
||||||
|
child: const CupertinoActivityIndicator(
|
||||||
|
animating: false,
|
||||||
|
radius: 35,
|
||||||
|
iOSVersionStyle:
|
||||||
|
CupertinoActivityIndicatorIOSVersionStyle.iOS14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
find.byKey(key),
|
||||||
|
matchesGoldenFile('activityIndicator.iOS14.paused.dark.png'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Activity indicator 0% in progress', (WidgetTester tester) async {
|
testWidgets('Activity indicator 0% in progress', (WidgetTester tester) async {
|
||||||
final Key key = UniqueKey();
|
final Key key = UniqueKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -79,7 +139,8 @@ void main() {
|
||||||
key: key,
|
key: key,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: CupertinoColors.white,
|
color: CupertinoColors.white,
|
||||||
child: const CupertinoActivityIndicator.partiallyRevealed(progress: 0),
|
child:
|
||||||
|
const CupertinoActivityIndicator.partiallyRevealed(progress: 0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -91,7 +152,8 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Activity indicator 30% in progress', (WidgetTester tester) async {
|
testWidgets('Activity indicator 30% in progress',
|
||||||
|
(WidgetTester tester) async {
|
||||||
final Key key = UniqueKey();
|
final Key key = UniqueKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Center(
|
Center(
|
||||||
|
@ -99,7 +161,9 @@ void main() {
|
||||||
key: key,
|
key: key,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: CupertinoColors.white,
|
color: CupertinoColors.white,
|
||||||
child: const CupertinoActivityIndicator.partiallyRevealed(progress: 0.5),
|
child: const CupertinoActivityIndicator.partiallyRevealed(
|
||||||
|
progress: 0.5,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -111,7 +175,8 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Activity indicator 100% in progress', (WidgetTester tester) async {
|
testWidgets('Activity indicator 100% in progress',
|
||||||
|
(WidgetTester tester) async {
|
||||||
final Key key = UniqueKey();
|
final Key key = UniqueKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Center(
|
Center(
|
||||||
|
@ -119,7 +184,8 @@ void main() {
|
||||||
key: key,
|
key: key,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: CupertinoColors.white,
|
color: CupertinoColors.white,
|
||||||
child: const CupertinoActivityIndicator.partiallyRevealed(progress: 1),
|
child:
|
||||||
|
const CupertinoActivityIndicator.partiallyRevealed(progress: 1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -148,7 +214,7 @@ void main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildCupertinoActivityIndicator([ bool animating ]) {
|
Widget buildCupertinoActivityIndicator([bool animating]) {
|
||||||
return MediaQuery(
|
return MediaQuery(
|
||||||
data: const MediaQueryData(platformBrightness: Brightness.light),
|
data: const MediaQueryData(platformBrightness: Brightness.light),
|
||||||
child: CupertinoActivityIndicator(
|
child: CupertinoActivityIndicator(
|
||||||
|
|
Loading…
Reference in a new issue