Newer
Older
// Copyright 2018 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 'package:flutter/widgets.dart';
import 'styling.dart';
/// This is a self-animated progress spinner, only instead of spinning it
/// moves five little circles in a horizontal arrangement.
class ThinkingIndicator extends ImplicitlyAnimatedWidget {
final Color color;
final double height;
final bool visible;
ThinkingIndicator({
this.color = const Color(0xffffffff),
this.height = 10.0,
this.visible = true,
Key? key,
}) : super(
duration: Styling.thinkingFadeDuration,
key: key,
);
@override
ImplicitlyAnimatedWidgetState createState() => _ThinkingIndicatorState();
}
class _ThinkingIndicatorState extends AnimatedWidgetBaseState<ThinkingIndicator> {
Tween<double>? _opacityTween;
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: widget.height,
child: Opacity(
opacity: _opacityTween!.evaluate(animation),
child: _opacityTween!.evaluate(animation) != 0
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
? _AnimatedCircles(
color: widget.color,
height: widget.height,
)
: null,
),
),
);
}
@override
void forEachTween(visitor) {
_opacityTween = visitor(
_opacityTween,
widget.visible ? 1.0 : 0.0,
(dynamic value) => Tween<double>(begin: value as double),
) as Tween<double>?;
}
}
class _AnimatedCircles extends StatefulWidget {
final Color color;
final double height;
const _AnimatedCircles({
required this.color,
required this.height,
Key? key,
}) : super(key: key);
@override
_AnimatedCirclesState createState() => _AnimatedCirclesState();
}
class _AnimatedCirclesState extends State<_AnimatedCircles>
with SingleTickerProviderStateMixin {
late Animation<double> _thinkingAnimation;
late AnimationController _thinkingController;
@override
void initState() {
super.initState();
_thinkingController =
AnimationController(duration: const Duration(milliseconds: 500), vsync: this)
..addStatusListener((status) {
// This bit ensures that the animation reverses course rather than
// stopping.
if (status == AnimationStatus.completed) _thinkingController.reverse();
if (status == AnimationStatus.dismissed) _thinkingController.forward();
});
_thinkingAnimation = Tween(begin: 0.0, end: widget.height)
.animate(CurvedAnimation(parent: _thinkingController, curve: Curves.easeOut));
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
_thinkingController.forward();
}
@override
void dispose() {
_thinkingController.dispose();
super.dispose();
}
Widget _buildCircle() {
return Container(
width: widget.height,
height: widget.height,
decoration: BoxDecoration(
border: Border.all(
color: widget.color,
width: 2.0,
),
borderRadius: BorderRadius.all(const Radius.circular(5.0)),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _thinkingAnimation,
builder: (context, child) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
SizedBox(width: _thinkingAnimation.value),
_buildCircle(),
],
);
},
);
}
}