progress_indicator.dart 7.95 KB
Newer Older
1 2 3 4 5 6
// 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;

7
import 'package:flutter/widgets.dart';
8 9

import 'theme.dart';
10 11

const double _kLinearProgressIndicatorHeight = 6.0;
12 13
const double _kMinCircularProgressIndicatorSize = 36.0;
const double _kCircularProgressIndicatorStrokeWidth = 4.0;
14

15
// TODO(hansmuller): implement the support for buffer indicator
Hixie's avatar
Hixie committed
16

17 18 19
abstract class ProgressIndicator extends StatefulComponent {
  ProgressIndicator({
    Key key,
Hixie's avatar
Hixie committed
20
    this.value
21 22
  }) : super(key: key);

23
  final double value; // Null for non-determinate progress indicator.
24

25
  Color _getBackgroundColor(BuildContext context) => Theme.of(context).backgroundColor;
26 27
  Color _getValueColor(BuildContext context) => Theme.of(context).primaryColor;

Hixie's avatar
Hixie committed
28 29 30 31
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%');
  }
32 33
}

34 35 36 37 38
class _LinearProgressIndicatorPainter extends CustomPainter {
  const _LinearProgressIndicatorPainter({
    this.backgroundColor,
    this.valueColor,
    this.value,
39
    this.animationValue
40 41 42 43 44
  });

  final Color backgroundColor;
  final Color valueColor;
  final double value;
45
  final double animationValue;
46

47
  void paint(Canvas canvas, Size size) {
48
    Paint paint = new Paint()
49
      ..color = backgroundColor
50
      ..style = PaintingStyle.fill;
51 52
    canvas.drawRect(Point.origin & size, paint);

53
    paint.color = valueColor;
54 55 56 57
    if (value != null) {
      double width = value.clamp(0.0, 1.0) * size.width;
      canvas.drawRect(Point.origin & new Size(width, size.height), paint);
    } else {
58
      double startX = size.width * (1.5 * animationValue - 0.5);
59 60 61 62 63 64 65
      double endX = startX + 0.5 * size.width;
      double x = startX.clamp(0.0, size.width);
      double width = endX.clamp(0.0, size.width) - x;
      canvas.drawRect(new Point(x, 0.0) & new Size(width, size.height), paint);
    }
  }

66 67 68 69
  bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
    return oldPainter.backgroundColor != backgroundColor
        || oldPainter.valueColor != valueColor
        || oldPainter.value != value
70
        || oldPainter.animationValue != animationValue;
71 72 73 74 75 76 77 78 79
  }
}

class LinearProgressIndicator extends ProgressIndicator {
  LinearProgressIndicator({
    Key key,
    double value
  }) : super(key: key, value: value);

80
  _LinearProgressIndicatorState createState() => new _LinearProgressIndicatorState();
81 82
}

83 84 85 86 87 88 89 90 91 92 93 94
class _LinearProgressIndicatorState extends State<LinearProgressIndicator> {
  Animation<double> _animation;
  AnimationController _controller;

  void initState() {
    super.initState();
    _controller = new AnimationController(
      duration: const Duration(milliseconds: 1500)
    )..repeat();
    _animation = new CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
  }

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  Widget _buildIndicator(BuildContext context, double animationValue) {
    return new Container(
      constraints: new BoxConstraints.tightFor(
        width: double.INFINITY,
        height: _kLinearProgressIndicatorHeight
      ),
      child: new CustomPaint(
        painter: new _LinearProgressIndicatorPainter(
          backgroundColor: config._getBackgroundColor(context),
          valueColor: config._getValueColor(context),
          value: config.value, // may be null
          animationValue: animationValue // ignored if config.value is not null
        )
      )
    );
  }

112 113
  Widget build(BuildContext context) {
    if (config.value != null)
114
      return _buildIndicator(context, _animation.value);
115 116 117 118

    return new AnimatedBuilder(
      animation: _animation,
      builder: (BuildContext context, Widget child) {
119
        return _buildIndicator(context, _animation.value);
120 121 122 123 124
      }
    );
  }
}

125
class _CircularProgressIndicatorPainter extends CustomPainter {
126
  static const _kTwoPI = math.PI * 2.0;
127
  static const _kEpsilon = .001;
128 129 130 131
  // Canavs.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close.
  static const _kSweep = _kTwoPI - _kEpsilon;
  static const _kStartAngle = -math.PI / 2.0;

132 133 134
  const _CircularProgressIndicatorPainter({
    this.valueColor,
    this.value,
135 136 137 138
    this.headValue,
    this.tailValue,
    this.stepValue,
    this.rotationValue
139 140 141 142
  });

  final Color valueColor;
  final double value;
143 144 145 146
  final double headValue;
  final double tailValue;
  final int stepValue;
  final double rotationValue;
147

148
  void paint(Canvas canvas, Size size) {
149
    Paint paint = new Paint()
150
      ..color = valueColor
151
      ..strokeWidth = _kCircularProgressIndicatorStrokeWidth
152
      ..style = PaintingStyle.stroke;
153 154

    if (value != null) {
155
      // Determinate
156
      double angle = value.clamp(0.0, 1.0) * _kSweep;
157
      Path path = new Path()
158 159
        ..arcTo(Point.origin & size, _kStartAngle, angle, false);
      canvas.drawPath(path, paint);
160

161
    } else {
162
      // Non-determinate
163
      paint.strokeCap = StrokeCap.square;
164 165

      double arcSweep = math.max(headValue * 3 / 2 * math.PI - tailValue * 3 / 2 * math.PI, _kEpsilon);
166
      Path path = new Path()
167 168 169 170
        ..arcTo(Point.origin & size,
                _kStartAngle + tailValue * 3 / 2 * math.PI + rotationValue * math.PI * 1.7 - stepValue * 0.8 * math.PI,
                arcSweep,
                false);
171 172 173 174
      canvas.drawPath(path, paint);
    }
  }

175
  bool shouldRepaint(_CircularProgressIndicatorPainter oldPainter) {
176 177
    return oldPainter.valueColor != valueColor
        || oldPainter.value != value
178 179 180 181
        || oldPainter.headValue != headValue
        || oldPainter.tailValue != tailValue
        || oldPainter.stepValue != stepValue
        || oldPainter.rotationValue != rotationValue;
182 183 184 185 186 187 188 189 190
  }
}

class CircularProgressIndicator extends ProgressIndicator {
  CircularProgressIndicator({
    Key key,
    double value
  }) : super(key: key, value: value);

191
  _CircularProgressIndicatorState createState() => new _CircularProgressIndicatorState();
192
}
193

194
// Tweens used by circular progress indicator
Hixie's avatar
Hixie committed
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
final Animatable<double> _kStrokeHeadTween = new CurveTween(
  curve: new Interval(0.0, 0.5, curve: Curves.fastOutSlowIn)
).chain(new CurveTween(
  curve: new SawTooth(5)
));

final Animatable<double> _kStrokeTailTween = new CurveTween(
  curve: new Interval(0.5, 1.0, curve: Curves.fastOutSlowIn)
).chain(new CurveTween(
  curve: new SawTooth(5)
));

final Animatable<int> _kStepTween = new StepTween(begin: 0, end: 5);

final Animatable<double> _kRotationTween = new CurveTween(curve: new SawTooth(5));
210 211

class _CircularProgressIndicatorState extends State<CircularProgressIndicator> {
212
  AnimationController _animationController;
213 214 215

  void initState() {
    super.initState();
216
    _animationController = new AnimationController(
217 218 219 220
      duration: const Duration(milliseconds: 6666)
    )..repeat();
  }

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  Widget _buildIndicator(BuildContext context, double headValue, double tailValue, int stepValue, double rotationValue) {
    return new Container(
      constraints: new BoxConstraints(
        minWidth: _kMinCircularProgressIndicatorSize,
        minHeight: _kMinCircularProgressIndicatorSize
      ),
      child: new CustomPaint(
        painter: new _CircularProgressIndicatorPainter(
          valueColor: config._getValueColor(context),
          value: config.value, // may be null
          headValue: headValue, // remaining arguments are ignored if config.value is not null
          tailValue: tailValue,
          stepValue: stepValue,
          rotationValue: rotationValue
        )
      )
    );
  }

240 241
  Widget build(BuildContext context) {
    if (config.value != null)
242
      return _buildIndicator(context, 0.0, 0.0, 0, 0.0);
243 244

    return new AnimatedBuilder(
245
      animation: _animationController,
246
      builder: (BuildContext context, Widget child) {
247 248 249 250 251 252 253
        return _buildIndicator(
          context,
          _kStrokeHeadTween.evaluate(_animationController),
          _kStrokeTailTween.evaluate(_animationController),
          _kStepTween.evaluate(_animationController),
          _kRotationTween.evaluate(_animationController)
        );
254 255 256 257
      }
    );
  }
}