I'd like to create a number counter that animates from a starting value to an end value. I've looked into using a Timer but can't seem to animate/update state properly. Including the decimal value would be great, but a simple integer animation is fine.
Number counter that needs to animate
double _mileCounter = 643.6;
_animateMileCounter() {
Duration duration = new Duration(milliseconds: 300);
return new Timer(duration, _updateMileCounter);
}
_updateMileCounter() {
setState(() {
_mileCounter += 1;
});
}
How would I increment the counter X number of times (with animation)? Similar to how a car's odometer increments.
For anyone still looking, you can use ImplicitlyAnimatedWidget.
Here is an example of an int counter. Works analogously for doubles.
class AnimatedCount extends ImplicitlyAnimatedWidget {
final int count;
AnimatedCount({
Key key,
#required this.count,
#required Duration duration,
Curve curve = Curves.linear
}) : super(duration: duration, curve: curve, key: key);
#override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _AnimatedCountState();
}
class _AnimatedCountState extends AnimatedWidgetBaseState<AnimatedCount> {
IntTween _count;
#override
Widget build(BuildContext context) {
return new Text(_count.evaluate(animation).toString());
}
#override
void forEachTween(TweenVisitor visitor) {
_count = visitor(_count, widget.count, (dynamic value) => new IntTween(begin: value));
}
}
Just rebuild the widget with a new value and it automatically animates there.
You should use an AnimationController with an AnimatedBuilder to rebuild your text when the controller changes. Here's an example that increments the miles when the floating action button is pressed (double.toStringAsFixed to get the decimal to show), with a curve on the animation speed:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(primarySwatch: Colors.purple),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
double _miles = 0.0;
#override initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_animation = _controller;
super.initState();
}
#override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
return new Scaffold(
body: new Material(
color: const Color.fromRGBO(246, 251, 8, 1.0),
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Text(
_animation.value.toStringAsFixed(1),
style: textTheme.display4.copyWith(fontStyle: FontStyle.italic),
);
},
),
new Text(
"MILES",
style: textTheme.display1.copyWith(fontStyle: FontStyle.italic),
)
],
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.directions_run),
onPressed: () {
Random rng = new Random();
setState(() {
_miles += rng.nextInt(20) + 0.3;
_animation = new Tween<double>(
begin: _animation.value,
end: _miles,
).animate(new CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _controller,
));
});
_controller.forward(from: 0.0);
}
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
You can use Countup package.
Countup(
begin: 0,
end: 7500,
duration: Duration(seconds: 3),
separator: ',',
style: TextStyle(
fontSize: 36,
),
)
https://pub.dev/packages/countup
You can simply use this plugin countup: ^0.1.3
import 'package:countup/countup.dart';
Countup(
begin: 100,
end: 8000,
duration: Duration(seconds: 3),
separator: ',',
style: TextStyle(
fontSize: 36,
fontweight : Fontweight.bold,
),
),
Related
I have multiple widgets I want to fade in one after another but without needing a button. Every tutorial I have seen has required the use of a button in order to transition to the next widget. The widgets I want to transition take up the whole screen .
How do you want to trigger the transition?
If time is a suitable trigger.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late Timer timer;
int count = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), (_) => setState(() => count += 1));
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$count',
key: ValueKey<int>(count),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
);
}
}
I have the following widget which will fit into a different parent widget into its body section. So in the parent widget I call this widget as below
body: MapsDemo(),. The issue now is that at this section I run an interval where every 30 seconds I want to call api to get all the latest markers.
Currently I print the count down as this codes print("${30 - timer.tick * 1}"); My issue is very simple I have 3 three floating action button and I have given them their id. Thus on each count down for the last floatingaction button which is btn3.text I am trying to set its value as ${30 - timer.tick * 1} but it does not work on this basis. How can I update the count down in the button?
class MapsDemo extends StatefulWidget {
#override
State createState() => MapsDemoState();
}
class MapsDemoState extends State<MapsDemo> {
GoogleMapController mapController;
#override
void initState() {
super.initState();
startTimer(30);
}
startTimer(int index) async {
print("Index30");
print(index);
new Timer.periodic(new Duration(seconds: 1), (timer) {
if ((30 / 1) >= timer.tick) {
print("${30 - timer.tick * 1}");
btn3.text = ${30 - timer.tick * 1};
} else {
timer.cancel();
var responseJson = NetworkUtils.getAllMarkers(
authToken
);
mapController.clearMarkers();
//startTimer(index + 1);
}
});
}
//Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.contacts]);import 'package:permission_handler/permission_handler.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
initialCameraPosition: new CameraPosition(target: LatLng(3.326411697920109, 102.13127606037108))
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.topRight,
child: Column(
children: <Widget>[
FloatingActionButton(
onPressed: () => print('button pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.map, size: 30.0),
heroTag: "btn1",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.add, size: 28.0),
heroTag: "btn2",
),
SizedBox(height: 5.0),
FloatingActionButton(
onPressed: () => print('second pressed'),
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor: Colors.lightBlue,
child: const Icon(Icons.directions_bike, size: 28.0),
heroTag: "btn3",
),
]
)
),
),
]
)
);
}
}
The below will display a countdown from 30 seconds inside of a FloatingActionButton.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FAB Countdown Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const _defaultSeconds = 30;
Timer _timer;
var _countdownSeconds = 0;
#override
void initState() {
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) => _getTime());
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
void _getTime() {
setState(() {
if (_countdownSeconds == 0) {
_countdownSeconds = _defaultSeconds;
} else {
_countdownSeconds--;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: const Text('FAB Countdown Demo'),
),
floatingActionButton: FloatingActionButton(
child: Text('$_countdownSeconds'), onPressed: () {}),
);
}
}
I want to show the animation of feedback of draggable when it is not accepted by the DropTarget.Flutter doesn't show the feedback. Is there any way we can show that or control it. Like this example, I want to achieve this effect. I somehow achieve this effect but it is not proper accurate returning to the original offset. It is moving ahead to its original position.
Animation effect I want.
Here is my Code, I have one drag box when I lift it to a certain position and leave him from there and it should animate back to original position, but it is returning to some other Offset like this.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: DragBox()),
);
}
}
class DragBox extends StatefulWidget {
DragBox({
Key key,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyDragBox();
}
}
class _MyDragBox extends State<DragBox> with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Offset begin;
Offset cancelledOffset;
Offset _offsetOfWidget;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((s) {
_afeteLayout();
});
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
}
void _afeteLayout() {
final RenderBox box = _globalKey.currentContext.findRenderObject();
Offset offset = -box.globalToLocal(Offset(0.0, 0.0));
_offsetOfWidget = offset;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: Draggable(
key: _globalKey,
onDraggableCanceled: (v, o) {
setState(() {
cancelledOffset = o;
});
_controller.forward();
},
feedback: Container(
color: Colors.red,
height: 50,
width: 50,
),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
),
),
_controller.isAnimating
? SlideTransition(
position: Tween<Offset>(
begin: cancelledOffset * 0.01,
end: _offsetOfWidget * 0.01)
.animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else {
_controller.reverse();
}
}),
child: Container(
color: Colors.red,
height: 50,
width: 50,
),
)
: Container(
child: Text('data'),
)
],
);
}
}
I think, this documentation about "Animate a widget using a physics simulation" is the closest example suited for what you are trying to achieve.
This recipe demonstrates how to move a widget from a dragged point
back to the center using a spring simulation.
To appreciate it in action, take a look on the example:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
/// A draggable card that moves back to [Alignment.center] when it's
/// released.
class DraggableCard extends StatefulWidget {
final Widget child;
DraggableCard({required this.child});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
/// The alignment of the card as it is dragged or being animated.
///
/// While the card is being dragged, this value is set to the values computed
/// in the GestureDetector onPanUpdate callback. If the animation is running,
/// this value is set to the value of the [_animation].
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
Sample output:
A simple way to solve this problem is to create a widget that overrides Draggable and make it a child of an AnimatedPositioned. Here is the example:
import 'package:flutter/material.dart';
class XDraggable extends StatefulWidget {
const XDraggable(
{Key? key,
required this.child,
required this.original_x,
required this.original_y,
this.animation_speed = 200})
: super(key: key);
final Widget child;
final double original_x;
final double original_y;
final double animation_speed;
#override
_XDraggableState createState() => _XDraggableState();
}
class _XDraggableState extends State<XDraggable> {
double x = 200;
double y = 200;
int animation_speed = 0;
#override
void initState() {
x = widget.original_x;
y = widget.original_y;
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: Duration(milliseconds: animation_speed),
child: Draggable(
onDragUpdate: (details) => {
setState(() {
animation_speed = 0;
x = x + details.delta.dx;
y = y + details.delta.dy;
}),
},
onDragEnd: (details) {
setState(() {
animation_speed = 200;
x = widget.original_x;
y = widget.original_y;
});
},
child: widget.child,
feedback: SizedBox(),
),
);
}
}
Then, just use the widget as a Stack child:
...
child: Stack(
fit: StackFit.expand,
children: [
XDraggable(
original_x: 20,
original_y: 20,
child: Container(
height: 50.0,
width: 50.0,
color: Colors.green,
),
)
],
),
...
I am trying to move the container on the screen by giving begin and end offset like from Offset(0.0,0.0) to Offset(400.0,300.0). I am using Slide Transition to animate the container I am using Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0)) to move it on the screen I want to pass these Offset(400.0,300.0) and animate it.
Here is my code
class MoveContainer extends StatefulWidget {
MoveContainer({Key key, }) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyMoveContainer();
}
}
class _MyMoveContainer extends State<MoveContainer>
with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Animation<Offset> _offset;
Offset local;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
);
_offset =
Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0))
.animate(_controller);
_offset.addListener(() {
setState(() {});
});
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: GestureDetector(
onPanStart: (start) {
RenderBox getBox = context.findRenderObject();
local = getBox.localToGlobal(start.globalPosition);
print('point are $local');
},
child: Container(
color: Colors.cyan,
height: 200.0,
width: 200.0,
child: Text("hello ")),
),
);
}
}
Probably this question is not actual for the author. (Asked 7 months ago).
But maybe my answer will help someone else.
Usually Slide Transition is used for transitions between pages. That is why, one unit of position value here is the size of one page. When you put there Offset(400.0,300.0) it's equal 400 screen right, and 300 pages down.
For your case it better to use AnimatedPositioned Widget.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: MoveContainer(),
),
);
}
}
class MoveContainer extends StatefulWidget {
#override
_MoveContainerState createState() => _MoveContainerState();
}
class _MoveContainerState extends State<MoveContainer> {
Offset offset = Offset.zero;
final double height = 200;
final double width = 200;
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) {
RenderBox getBox = context.findRenderObject();
setState(() {
offset = getBox.localToGlobal(details.globalPosition);
});
},
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: 300),
top: offset.dy - (height / 2),
left: offset.dx - (width / 2),
child: Container(
color: Colors.cyan,
height: height,
width: width,
child: Text("hello "),
),
),
],
),
);
}
}
I am trying to create an animation that "pops" the widget to the front and returns to it
import "package:flutter/material.dart";
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter> with SingleTickerProviderStateMixin{
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
child: new Container(width: 200.0, height: 200.0, color: Colors.green),
builder: (BuildContext context, Widget child) {
//What to return that scales the element
},
);
}
}
For rotating, I would use a Transform and return a Matrix. But what should I return to accomplish the scaling animation?
Thanks in advance
Just as a alternative, to set just scale for a specific Widget.
Transform.scale(
scale: 1.0,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.pink,
),
),
If you're going to size your contents manually you could just read controller.value in your builder function and use that to set the size of the container.
Alternatively, you could consider a pair of SizeTransitions for each axis. The Align class may also be useful because you can set a sizeFactor in each dimension.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new HomePage(),
));
}
class HomePage extends StatefulWidget {
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.sort),
onPressed: () {},
),
body: new Center(
child: new ScoreCounter(),
),
);
}
}
class ScoreCounter extends StatefulWidget {
#override
_ScoreCounter createState() => new _ScoreCounter();
}
class _ScoreCounter extends State<ScoreCounter>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)
..forward();
}
#override
build(BuildContext context){
return new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
double size = _controller.value * 200.0;
return new Container(width: size, height: size, color: Colors.green);
},
);
}
}
Another Approach is to create generalized Scale Transformation.
Simply add this method to your component
Matrix4 scaleXYZTransform({
double scaleX = 1.00,
double scaleY = 1.00,
double scaleZ = 1.00,
}) {
return Matrix4.diagonal3Values(scaleX, scaleY, scaleZ);
}
Now you can easily Scale any widget by wrapping it:
Transform(
transform: scaleXYZTransform(scaleX: .5, scaleY: .5),
child: Container(
child: myAwesomeWidget(),
));