blob: e8efbf48696fc71b5f68f401f4debbae663189f0 [file] [log] [blame]
// Copyright 2014 The Flutter 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/foundation.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
const double _kDefaultIndicatorRadius = 10.0;
// Extracted from iOS 13.2 Beta.
const Color _kActiveTickColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF3C3C44),
darkColor: Color(0xFFEBEBF5),
);
/// An iOS-style activity indicator that spins clockwise.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=AENVH-ZqKDQ}
///
/// {@tool dartpad}
/// This example shows how [CupertinoActivityIndicator] can be customized.
///
/// ** See code in examples/api/lib/cupertino/activity_indicator/cupertino_activity_indicator.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoLinearActivityIndicator], which displays progress along a line.
/// * <https://developer.apple.com/design/human-interface-guidelines/progress-indicators/>
class CupertinoActivityIndicator extends StatefulWidget {
/// Creates an iOS-style activity indicator that spins clockwise.
const CupertinoActivityIndicator({
super.key,
this.color,
this.animating = true,
this.radius = _kDefaultIndicatorRadius,
}) : assert(radius > 0.0),
progress = 1.0;
/// Creates a non-animated iOS-style activity indicator that displays
/// a partial count of ticks based on the value of [progress].
///
/// When provided, the value of [progress] must be between 0.0 (zero ticks
/// will be shown) and 1.0 (all ticks will be shown) inclusive. Defaults
/// to 1.0.
const CupertinoActivityIndicator.partiallyRevealed({
super.key,
this.color,
this.radius = _kDefaultIndicatorRadius,
this.progress = 1.0,
}) : assert(radius > 0.0),
assert(progress >= 0.0),
assert(progress <= 1.0),
animating = false;
/// Color of the activity indicator.
///
/// Defaults to color extracted from native iOS.
final Color? color;
/// Whether the activity indicator is running its animation.
///
/// Defaults to true.
final bool animating;
/// Radius of the spinner widget.
///
/// Defaults to 10 pixels. Must be positive.
final double radius;
/// Determines the percentage of spinner ticks that will be shown. Typical usage would
/// display all ticks, however, this allows for more fine-grained control such as
/// during pull-to-refresh when the drag-down action shows one tick at a time as
/// the user continues to drag down.
///
/// Defaults to one. Must be between zero and one, inclusive.
final double progress;
@override
State<CupertinoActivityIndicator> createState() => _CupertinoActivityIndicatorState();
}
class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: const Duration(seconds: 1), vsync: this);
if (widget.animating) {
_controller.repeat();
}
}
@override
void didUpdateWidget(CupertinoActivityIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.animating != oldWidget.animating) {
if (widget.animating) {
_controller.repeat();
} else {
_controller.stop();
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: widget.radius * 2,
width: widget.radius * 2,
child: CustomPaint(
painter: _CupertinoActivityIndicatorPainter(
position: _controller,
activeColor: widget.color ?? CupertinoDynamicColor.resolve(_kActiveTickColor, context),
radius: widget.radius,
progress: widget.progress,
),
),
);
}
}
const double _kTwoPI = math.pi * 2.0;
/// Alpha values extracted from the native component (for both dark and light mode) to
/// draw the spinning ticks.
const List<int> _kAlphaValues = <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;
class _CupertinoActivityIndicatorPainter extends CustomPainter {
_CupertinoActivityIndicatorPainter({
required this.position,
required this.activeColor,
required this.radius,
required this.progress,
}) : tickFundamentalShape = RRect.fromLTRBXY(
-radius / _kDefaultIndicatorRadius,
-radius / 3.0,
radius / _kDefaultIndicatorRadius,
-radius,
radius / _kDefaultIndicatorRadius,
radius / _kDefaultIndicatorRadius,
),
super(repaint: position);
final Animation<double> position;
final Color activeColor;
final double radius;
final double progress;
// Use a RRect instead of RSuperellipse since this shape is really small
// and should make little visual difference.
final RRect tickFundamentalShape;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint();
final int tickCount = _kAlphaValues.length;
canvas.save();
canvas.translate(size.width / 2.0, size.height / 2.0);
final int activeTick = (tickCount * position.value).floor();
for (var i = 0; i < tickCount * progress; ++i) {
final int t = (i - activeTick) % tickCount;
paint.color = activeColor.withAlpha(
progress < 1 ? _partiallyRevealedAlpha : _kAlphaValues[t],
);
canvas.drawRRect(tickFundamentalShape, paint);
canvas.rotate(_kTwoPI / tickCount);
}
canvas.restore();
}
@override
bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
return oldPainter.position != position ||
oldPainter.activeColor != activeColor ||
oldPainter.progress != progress;
}
}
/// An iOS-style linear activity indicator.
///
/// The [CupertinoLinearActivityIndicator] is a linear progress bar that
/// displays a colored bar to indicate the progress of an ongoing task.
///
/// {@tool dartpad}
/// This example shows how [CupertinoLinearActivityIndicator] can be customized.
///
/// ** See code in examples/api/lib/cupertino/activity_indicator/cupertino_linear_activity_indicator.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoActivityIndicator], which is an iOS-style activity indicator that spins clockwise.
/// * <https://developer.apple.com/design/human-interface-guidelines/progress-indicators/>
class CupertinoLinearActivityIndicator extends StatelessWidget {
/// Creates a linear iOS-style activity indicator.
const CupertinoLinearActivityIndicator({
super.key,
required this.progress,
this.height = 4.5,
this.color,
}) : assert(height > 0),
assert(progress >= 0.0 && progress <= 1.0);
/// The current progress of the linear activity indicator.
///
/// This value must be between 0.0 and 1.0. A value of 0.0 means no progress
/// and 1.0 means that progress is complete.
final double progress;
/// The height of the line used to draw the linear activity indicator.
///
/// Defaults to 4.5 units. Must be positive.
final double height;
/// The color of the progress bar.
///
/// This color represents the portion of the bar that indicates progress.
///
/// Defaults to [CupertinoColors.activeBlue] if no color is specified.
final Color? color;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(minHeight: height, minWidth: double.infinity),
child: CustomPaint(
painter: _CupertinoLinearActivityIndicator(progress: progress, color: color),
),
);
}
}
class _CupertinoLinearActivityIndicator extends CustomPainter {
_CupertinoLinearActivityIndicator({required this.progress, this.color})
: _backgroundPaint = Paint()
..color = CupertinoColors.systemFill
..style = PaintingStyle.fill,
_progressPaint = Paint()
..color = color ?? CupertinoColors.activeBlue
..style = PaintingStyle.fill;
final double progress;
final Color? color;
/// The background paint used to draw the full width of the progress bar.
///
/// This paint object is created once and reused to fill the background
/// with a system fill color.
final Paint _backgroundPaint;
/// The paint used to draw the progress portion of the progress bar.
///
/// This paint object is created once and reused to fill the progress area.
final Paint _progressPaint;
@override
void paint(Canvas canvas, Size size) {
// Draw the background of the progress bar.
canvas.drawRRect(
BorderRadius.all(Radius.circular(size.height / 2)).toRRect(Offset.zero & size),
_backgroundPaint,
);
// Draw the progress portion of the bar.
if (progress > 0) {
canvas.drawRRect(
BorderRadius.all(
Radius.circular(size.height / 2),
).toRRect(Offset.zero & Size(clampDouble(progress, 0.0, 1.0) * size.width, size.height)),
_progressPaint,
);
}
}
@override
bool shouldRepaint(_CupertinoLinearActivityIndicator old) =>
old.progress != progress || old.color != color;
}