mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Minor fixes throughout, e.g. removing trailing commas from the end of sample code expressions, changing headings to "sample code" more consistently, removing stale todos, fix typos in a private method name, minor grammar fixes, added some clarifications to CircularProgressIndicator, LinearProgressIndicator, CrossAxisAlignment, added some cross-references to various members, made it slightly clearer that layout algorithms are implementation details. Clarified "elevation" throughout. Added docs to InkResponse and InkWell. Added sample code for: SliverAppBar, Card, ListTile, EdgeInsets, Row, Column, CustomScrollView, ListView, SliverFixedExtentList, and SliverGrid. Fixes #10317. Fixes #10316. Fixes #10267. (sort of, see comment therein) Fixes #9331. (sort of, see comment therein) Fixes #9407. (sort of, see comment therein)
509 lines
17 KiB
Dart
509 lines
17 KiB
Dart
// 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/widgets.dart';
|
|
|
|
import 'material.dart';
|
|
import 'theme.dart';
|
|
|
|
const double _kLinearProgressIndicatorHeight = 6.0;
|
|
const double _kMinCircularProgressIndicatorSize = 36.0;
|
|
|
|
// TODO(hansmuller): implement the support for buffer indicator
|
|
|
|
/// A base class for material design progress indicators.
|
|
///
|
|
/// This widget cannot be instantiated directly. For a linear progress
|
|
/// indicator, see [LinearProgressIndicator]. For a circular progress indicator,
|
|
/// see [CircularProgressIndicator].
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * <https://material.google.com/components/progress-activity.html>
|
|
abstract class ProgressIndicator extends StatefulWidget {
|
|
/// Creates a progress indicator.
|
|
///
|
|
/// The [value] argument can be either null (corresponding to an indeterminate
|
|
/// progress indcator) or non-null (corresponding to a determinate progress
|
|
/// indicator). See [value] for details.
|
|
const ProgressIndicator({
|
|
Key key,
|
|
this.value,
|
|
this.backgroundColor,
|
|
this.valueColor,
|
|
}) : super(key: key);
|
|
|
|
/// If non-null, the value of this progress indicator with 0.0 corresponding
|
|
/// to no progress having been made and 1.0 corresponding to all the progress
|
|
/// having been made.
|
|
///
|
|
/// If null, this progress indicator is indeterminate, which means the
|
|
/// indicator displays a predetermined animation that does not indicator how
|
|
/// much actual progress is being made.
|
|
final double value;
|
|
|
|
/// The progress indicator's background color. The current theme's
|
|
/// [ThemeData.backgroundColor] by default.
|
|
final Color backgroundColor;
|
|
|
|
/// The indicator's color is the animation's value. To specify a constant
|
|
/// color use: `new AlwaysStoppedAnimation<Color>(color)`.
|
|
///
|
|
/// If null, the progress indicator is rendered with the current theme's
|
|
/// [ThemeData.accentColor].
|
|
final Animation<Color> valueColor;
|
|
|
|
Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
|
|
Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
|
|
|
|
@override
|
|
void debugFillDescription(List<String> description) {
|
|
super.debugFillDescription(description);
|
|
if (value != null) {
|
|
description.add('${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%');
|
|
} else {
|
|
description.add('<indeterminate>');
|
|
}
|
|
}
|
|
}
|
|
|
|
class _LinearProgressIndicatorPainter extends CustomPainter {
|
|
const _LinearProgressIndicatorPainter({
|
|
this.backgroundColor,
|
|
this.valueColor,
|
|
this.value,
|
|
this.animationValue,
|
|
});
|
|
|
|
final Color backgroundColor;
|
|
final Color valueColor;
|
|
final double value;
|
|
final double animationValue;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final Paint paint = new Paint()
|
|
..color = backgroundColor
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawRect(Offset.zero & size, paint);
|
|
|
|
paint.color = valueColor;
|
|
if (value != null) {
|
|
final double width = value.clamp(0.0, 1.0) * size.width;
|
|
canvas.drawRect(Offset.zero & new Size(width, size.height), paint);
|
|
} else {
|
|
final double startX = size.width * (1.5 * animationValue - 0.5);
|
|
final double endX = startX + 0.5 * size.width;
|
|
final double x = startX.clamp(0.0, size.width);
|
|
final double width = endX.clamp(0.0, size.width) - x;
|
|
canvas.drawRect(new Offset(x, 0.0) & new Size(width, size.height), paint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
|
|
return oldPainter.backgroundColor != backgroundColor
|
|
|| oldPainter.valueColor != valueColor
|
|
|| oldPainter.value != value
|
|
|| oldPainter.animationValue != animationValue;
|
|
}
|
|
}
|
|
|
|
/// A material design linear progress indicator, also known as a progress bar.
|
|
///
|
|
/// A widget that shows progress along a line. There are two kinds of linear
|
|
/// progress indicators:
|
|
///
|
|
/// * _Determinate_. Determinate progress indicators have a specific value at
|
|
/// each point in time, and the value should increase monotonically from 0.0
|
|
/// to 1.0, at which time the indicator is complete. To create a determinate
|
|
/// progress indicator, use a non-null [value] between 0.0 and 1.0.
|
|
/// * _Indeterminate_. Indeterminate progress indicators do not have a specific
|
|
/// value at each point in time and instead indicate that progress is being
|
|
/// made without indicating how much progress remains. To create an
|
|
/// indeterminate progress indicator, use a null [value].
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [CircularProgressIndicator]
|
|
/// * <https://material.google.com/components/progress-activity.html#progress-activity-types-of-indicators>
|
|
class LinearProgressIndicator extends ProgressIndicator {
|
|
/// Creates a linear progress indicator.
|
|
///
|
|
/// The [value] argument can be either null (corresponding to an indeterminate
|
|
/// progress indcator) or non-null (corresponding to a determinate progress
|
|
/// indicator). See [value] for details.
|
|
const LinearProgressIndicator({
|
|
Key key,
|
|
double value,
|
|
}) : super(key: key, value: value);
|
|
|
|
@override
|
|
_LinearProgressIndicatorState createState() => new _LinearProgressIndicatorState();
|
|
}
|
|
|
|
class _LinearProgressIndicatorState extends State<LinearProgressIndicator> with SingleTickerProviderStateMixin {
|
|
Animation<double> _animation;
|
|
AnimationController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = new AnimationController(
|
|
duration: const Duration(milliseconds: 1500),
|
|
vsync: this,
|
|
)..repeat();
|
|
_animation = new CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Widget _buildIndicator(BuildContext context, double animationValue) {
|
|
return new Container(
|
|
constraints: const BoxConstraints.tightFor(
|
|
width: double.INFINITY,
|
|
height: _kLinearProgressIndicatorHeight,
|
|
),
|
|
child: new CustomPaint(
|
|
painter: new _LinearProgressIndicatorPainter(
|
|
backgroundColor: widget._getBackgroundColor(context),
|
|
valueColor: widget._getValueColor(context),
|
|
value: widget.value, // may be null
|
|
animationValue: animationValue, // ignored if widget.value is not null
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.value != null)
|
|
return _buildIndicator(context, _animation.value);
|
|
|
|
return new AnimatedBuilder(
|
|
animation: _animation,
|
|
builder: (BuildContext context, Widget child) {
|
|
return _buildIndicator(context, _animation.value);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CircularProgressIndicatorPainter extends CustomPainter {
|
|
static const double _kTwoPI = math.PI * 2.0;
|
|
static const double _kEpsilon = .001;
|
|
// Canavs.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close.
|
|
static const double _kSweep = _kTwoPI - _kEpsilon;
|
|
static const double _kStartAngle = -math.PI / 2.0;
|
|
|
|
_CircularProgressIndicatorPainter({
|
|
this.valueColor,
|
|
this.value,
|
|
this.headValue,
|
|
this.tailValue,
|
|
this.stepValue,
|
|
this.rotationValue,
|
|
this.strokeWidth,
|
|
}) : arcStart = value != null
|
|
? _kStartAngle
|
|
: _kStartAngle + tailValue * 3 / 2 * math.PI + rotationValue * math.PI * 1.7 - stepValue * 0.8 * math.PI,
|
|
arcSweep = value != null
|
|
? value.clamp(0.0, 1.0) * _kSweep
|
|
: math.max(headValue * 3 / 2 * math.PI - tailValue * 3 / 2 * math.PI, _kEpsilon);
|
|
|
|
final Color valueColor;
|
|
final double value;
|
|
final double headValue;
|
|
final double tailValue;
|
|
final int stepValue;
|
|
final double rotationValue;
|
|
final double strokeWidth;
|
|
final double arcStart;
|
|
final double arcSweep;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final Paint paint = new Paint()
|
|
..color = valueColor
|
|
..strokeWidth = strokeWidth
|
|
..style = PaintingStyle.stroke;
|
|
|
|
if (value == null) // Indeterminate
|
|
paint.strokeCap = StrokeCap.square;
|
|
|
|
canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(_CircularProgressIndicatorPainter oldPainter) {
|
|
return oldPainter.valueColor != valueColor
|
|
|| oldPainter.value != value
|
|
|| oldPainter.headValue != headValue
|
|
|| oldPainter.tailValue != tailValue
|
|
|| oldPainter.stepValue != stepValue
|
|
|| oldPainter.rotationValue != rotationValue
|
|
|| oldPainter.strokeWidth != strokeWidth;
|
|
}
|
|
}
|
|
|
|
/// A material design circular progress indicator, which spins to indicate that
|
|
/// the application is busy.
|
|
///
|
|
/// A widget that shows progress along a circle. There are two kinds of circular
|
|
/// progress indicators:
|
|
///
|
|
/// * _Determinate_. Determinate progress indicators have a specific value at
|
|
/// each point in time, and the value should increase monotonically from 0.0
|
|
/// to 1.0, at which time the indicator is complete. To create a determinate
|
|
/// progress indicator, use a non-null [value] between 0.0 and 1.0.
|
|
/// * _Indeterminate_. Indeterminate progress indicators do not have a specific
|
|
/// value at each point in time and instead indicate that progress is being
|
|
/// made without indicating how much progress remains. To create an
|
|
/// indeterminate progress indicator, use a null [value].
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [LinearProgressIndicator]
|
|
/// * <https://material.google.com/components/progress-activity.html#progress-activity-types-of-indicators>
|
|
class CircularProgressIndicator extends ProgressIndicator {
|
|
/// Creates a circular progress indicator.
|
|
///
|
|
/// The [value] argument can be either null (corresponding to an indeterminate
|
|
/// progress indcator) or non-null (corresponding to a determinate progress
|
|
/// indicator). See [value] for details.
|
|
const CircularProgressIndicator({
|
|
Key key,
|
|
double value,
|
|
Color backgroundColor,
|
|
Animation<Color> valueColor,
|
|
this.strokeWidth: 4.0,
|
|
}) : super(key: key, value: value, backgroundColor: backgroundColor, valueColor: valueColor);
|
|
|
|
/// The width of the line used to draw the circle.
|
|
final double strokeWidth;
|
|
|
|
@override
|
|
_CircularProgressIndicatorState createState() => new _CircularProgressIndicatorState();
|
|
}
|
|
|
|
// Tweens used by circular progress indicator
|
|
final Animatable<double> _kStrokeHeadTween = new CurveTween(
|
|
curve: const Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
|
|
).chain(new CurveTween(
|
|
curve: const SawTooth(5),
|
|
));
|
|
|
|
final Animatable<double> _kStrokeTailTween = new CurveTween(
|
|
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
|
|
).chain(new CurveTween(
|
|
curve: const SawTooth(5),
|
|
));
|
|
|
|
final Animatable<int> _kStepTween = new StepTween(begin: 0, end: 5);
|
|
|
|
final Animatable<double> _kRotationTween = new CurveTween(curve: const SawTooth(5));
|
|
|
|
class _CircularProgressIndicatorState extends State<CircularProgressIndicator> with SingleTickerProviderStateMixin {
|
|
AnimationController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = new AnimationController(
|
|
duration: const Duration(milliseconds: 6666),
|
|
vsync: this,
|
|
)..repeat();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, int stepValue, double rotationValue) {
|
|
return new Container(
|
|
constraints: const BoxConstraints(
|
|
minWidth: _kMinCircularProgressIndicatorSize,
|
|
minHeight: _kMinCircularProgressIndicatorSize,
|
|
),
|
|
child: new CustomPaint(
|
|
painter: new _CircularProgressIndicatorPainter(
|
|
valueColor: widget._getValueColor(context),
|
|
value: widget.value, // may be null
|
|
headValue: headValue, // remaining arguments are ignored if widget.value is not null
|
|
tailValue: tailValue,
|
|
stepValue: stepValue,
|
|
rotationValue: rotationValue,
|
|
strokeWidth: widget.strokeWidth,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAnimation() {
|
|
return new AnimatedBuilder(
|
|
animation: _controller,
|
|
builder: (BuildContext context, Widget child) {
|
|
return _buildIndicator(
|
|
context,
|
|
_kStrokeHeadTween.evaluate(_controller),
|
|
_kStrokeTailTween.evaluate(_controller),
|
|
_kStepTween.evaluate(_controller),
|
|
_kRotationTween.evaluate(_controller),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.value != null)
|
|
return _buildIndicator(context, 0.0, 0.0, 0, 0.0);
|
|
return _buildAnimation();
|
|
}
|
|
}
|
|
|
|
class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter {
|
|
_RefreshProgressIndicatorPainter({
|
|
Color valueColor,
|
|
double value,
|
|
double headValue,
|
|
double tailValue,
|
|
int stepValue,
|
|
double rotationValue,
|
|
double strokeWidth,
|
|
this.arrowheadScale,
|
|
}) : super(
|
|
valueColor: valueColor,
|
|
value: value,
|
|
headValue: headValue,
|
|
tailValue: tailValue,
|
|
stepValue: stepValue,
|
|
rotationValue: rotationValue,
|
|
strokeWidth: strokeWidth,
|
|
);
|
|
|
|
final double arrowheadScale;
|
|
|
|
void paintArrowhead(Canvas canvas, Size size) {
|
|
// ux, uy: a unit vector whose direction parallels the base of the arrowhead.
|
|
// Note that ux, -uy points in the direction the arrowhead points.
|
|
final double arcEnd = arcStart + arcSweep;
|
|
final double ux = math.cos(arcEnd);
|
|
final double uy = math.sin(arcEnd);
|
|
|
|
assert(size.width == size.height);
|
|
final double radius = size.width / 2.0;
|
|
final double arrowheadPointX = radius + ux * radius + -uy * strokeWidth * 2.0 * arrowheadScale;
|
|
final double arrowheadPointY = radius + uy * radius + ux * strokeWidth * 2.0 * arrowheadScale;
|
|
final double arrowheadRadius = strokeWidth * 1.5 * arrowheadScale;
|
|
final double innerRadius = radius - arrowheadRadius;
|
|
final double outerRadius = radius + arrowheadRadius;
|
|
|
|
final Path path = new Path()
|
|
..moveTo(radius + ux * innerRadius, radius + uy * innerRadius)
|
|
..lineTo(radius + ux * outerRadius, radius + uy * outerRadius)
|
|
..lineTo(arrowheadPointX, arrowheadPointY)
|
|
..close();
|
|
final Paint paint = new Paint()
|
|
..color = valueColor
|
|
..strokeWidth = strokeWidth
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
super.paint(canvas, size);
|
|
if (arrowheadScale > 0.0)
|
|
paintArrowhead(canvas, size);
|
|
}
|
|
}
|
|
|
|
/// An indicator for the progress of refreshing the contents of a widget.
|
|
///
|
|
/// Typically used for swipe-to-refresh interactions. See [RefreshIndicator] for
|
|
/// a complete implementation of swipe-to-refresh driven by a [Scrollable]
|
|
/// widget.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [RefreshIndicator]
|
|
class RefreshProgressIndicator extends CircularProgressIndicator {
|
|
/// Creates a refresh progress indicator.
|
|
///
|
|
/// Rather than creating a refresh progress indicator directly, consider using
|
|
/// a [RefreshIndicator] together with a [Scrollable] widget.
|
|
const RefreshProgressIndicator({
|
|
Key key,
|
|
double value,
|
|
Color backgroundColor,
|
|
Animation<Color> valueColor,
|
|
double strokeWidth: 2.0, // Different default than CircularProgressIndicator.
|
|
}) : super(
|
|
key: key,
|
|
value: value,
|
|
backgroundColor: backgroundColor,
|
|
valueColor: valueColor,
|
|
strokeWidth: strokeWidth,
|
|
);
|
|
|
|
@override
|
|
_RefreshProgressIndicatorState createState() => new _RefreshProgressIndicatorState();
|
|
}
|
|
|
|
class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
|
|
static const double _kIndicatorSize = 40.0;
|
|
|
|
// Always show the indeterminate version of the circular progress indicator.
|
|
// When value is non-null the sweep of the progress indicator arrow's arc
|
|
// varies from 0 to about 270 degrees. When value is null the arrow animates
|
|
// starting from wherever we left it.
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.value != null)
|
|
_controller.value = widget.value / 10.0;
|
|
else
|
|
_controller.forward();
|
|
return _buildAnimation();
|
|
}
|
|
|
|
@override
|
|
Widget _buildIndicator(BuildContext context, double headValue, double tailValue, int stepValue, double rotationValue) {
|
|
final double arrowheadScale = widget.value == null ? 0.0 : (widget.value * 2.0).clamp(0.0, 1.0);
|
|
return new Container(
|
|
width: _kIndicatorSize,
|
|
height: _kIndicatorSize,
|
|
margin: const EdgeInsets.all(4.0), // acommodate the shadow
|
|
child: new Material(
|
|
type: MaterialType.circle,
|
|
color: widget.backgroundColor ?? Theme.of(context).canvasColor,
|
|
elevation: 2.0,
|
|
child: new Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: new CustomPaint(
|
|
painter: new _RefreshProgressIndicatorPainter(
|
|
valueColor: widget._getValueColor(context),
|
|
value: null, // Draw the indeterminate progress indicator.
|
|
headValue: headValue,
|
|
tailValue: tailValue,
|
|
stepValue: stepValue,
|
|
rotationValue: rotationValue,
|
|
strokeWidth: widget.strokeWidth,
|
|
arrowheadScale: arrowheadScale,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|