this is my animation code.
late AnimationController _controller;
late Animation<Color?> _color;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 1350),
vsync: this,
)..forward();
_color = ColorTween(begin: Colors.grey.shade400, end: Colors.amber)
.animate(_controller);
}
and I make Grid and apply animationBuilder.
GridView.builder(
...
child: cardButtons(subject[index], _color),)
return GestureDetector(
onTap: () {
doMultiSelection(item.toString());
setState(() {});
},
child: AnimatedBuilder(
animation: _color,
builder: (context, child) {
return Card(
// check if the index is equal to the selected Card integer
color: selectedItem.contains(item.toString())
? _color.value
: Colors.grey.shade400,
child: Container(
height: 200,
width: 200,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
I want to active animation(_color.value) whenever i click.
But it can olny work one time. How can I fix it?
Related
I new to flutter and i have a counter button that i want to prevent it from multiple touch.
The Tap Function is defined under Inkwell component (onTap: () => counterBloc.doCount(context)).
if i run this apps and doing multi touch, counter will go up quickly, but i dont want it happen. any idea ?
below are my code :
Expanded(
child: Container(
padding: EdgeInsets.only(right: 16),
alignment: Alignment.centerRight,
child: InkWell(
onTap: () => counterBloc.doCount(context),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Image.asset("assets/images/home/tap.png", scale: 11,),
StreamBuilder(
initialData: 0,
stream: counterBloc.counterStream,
builder: (BuildContext ctx, AsyncSnapshot<int> snapshot){
return Text("${snapshot.data}",style: TextStyle(color: Colors.white, fontSize: 120),);
},
),
],
)
)
)
)
you can use an AbsorbPointer
AbsorbPointer(
absorbing: !enabled,
child: InkWell(
onTap: (){
print('buttonClicked');
setState(() {
enabled = false;
});
},
child: Container(
width: 50.0,
height: 50.0,
color: Colors.red,
),
),
),
and when you want to enable the button again, set the enabled to true, don't forget to wrap it with a setState
Try this? It should solve your problem.
class SafeOnTap extends StatefulWidget {
SafeOnTap({
Key? key,
required this.child,
required this.onSafeTap,
this.intervalMs = 500,
}) : super(key: key);
final Widget child;
final GestureTapCallback onSafeTap;
final int intervalMs;
#override
_SafeOnTapState createState() => _SafeOnTapState();
}
class _SafeOnTapState extends State<SafeOnTap> {
int lastTimeClicked = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - lastTimeClicked < widget.intervalMs) {
return;
}
lastTimeClicked = now;
widget.onSafeTap();
},
child: widget.child,
);
}
}
You can wrap any kind of widget if you want.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
// every click need to wait for 500ms
SafeOnTap(
onSafeTap: () => log('500ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('500ms click me')),
),
),
// every click need to wait for 2000ms
SafeOnTap(
intervalMs: 2000,
onSafeTap: () => log('2000ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('2000ms click me')),
),
),
],
),
),
),
);
}
}
Another option is to use debouncing to prevent this kind of behaviour ie with easy_debounce, or implementing your own debounce.
You can also use IgnorePointer
IgnorePointer(
ignoring: !isEnabled
child: yourChildWidget
)
And when you disable the component, it starts ignoring the touches within the boundary of the widget.
I personally wouldn't rely on setState, I'd go with a simple solution like this:
Widget createMultiClickPreventedButton(String text, VoidCallback clickHandler) {
var clicked = false;
return ElevatedButton(
child: Text(text),
onPressed: () {
if (!clicked) {
clicked = true;
clickHandler.call();
}
});
}
You can also use a Stream to make counter to count only on debounced taps.
final BehaviourSubject onTapStream = BehaviourSubject()
#override
void initState() {
super.initState();
// Debounce your taps here
onTapStream.debounceTime(const Duration(milliseconds: 300)).listen((_) {
// Do something on tap
print(1);
});
}
I'm creating a dashboard which contain Tween animation for two widgets, Text and two Container. But, I want to make the two Container's opacity changing slowly from invisible to visible...so I used AnimatedOpacity. But I don't know how to do it...
Any help would be appreciated..
class _IntroState extends State<Intro> with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween(begin: -1.0, end: 0.0).animate(CurvedAnimation(
parent: animationController, curve: Curves.fastOutSlowIn));
animationController.forward();
}
#override
Widget build(BuildContext context) {
bool _visible = false;
final double width = MediaQuery.of(context).size.width;
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Scaffold(
//BODDY
body: ListView(
hildren:<Widget>[
new Stack(
children: <Widget>[
new Transform(
//ANIMATED OPACITY
new AnimatedOpacity(
opacity: _visible ? 0.0 : 1.0,
duration: Duration(milliseconds: 500),
child: new Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),
child: new Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Container(
child: Column(
children: <Widget>[
//THIS THE CONTAINER
new Container(. . .),
new Container(. . .)
Instead of AnimatedOpacity, use a FadeTransition widget. This gives you manual control over the animation:
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animationController.drive(CurveTween(curve: Curves.easeOut)),
child: ...,
);
}
To make a StatelessWidget or StatefulWidget fade in automatically on creation, TweenAnimationBuilder provides an even easier solution:
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: Curves.ease,
duration: const Duration(seconds: 1),
builder: (BuildContext context, double opacity, Widget? child) {
return Opacity(
opacity: opacity,
child: Container(width: 20, height: 20, color: Colors.red)
);
});
}
}
See my Codepen for a complete example: https://codepen.io/atok/pen/BaZVRPr
Best regards
For anyone who'd like to fade a widget automatically as soon as the page is rendered and still wants to use AnimatedOpacity, you can put the call to change the state of the opacity in the WidgetsBinding's addPostFrameCallback callback.
Put this code below in your initState.
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_opacity = 1;
});
});
I totally recommend using #boformer 's answer above.
But, I played around with your code and wanted to show you how you can call setState to trigger the AnimatedOpacity, so you can see that it is working without onTap or GestureDetector as you were thinking in the comments above.
I got your code and played around with it. What I did is, simply added a status listener to your animation controller and when the controller is done. I triggered the visibility boolean in setState. Then it will change the visibility of the containers.
// When animation finished change the visibility.
animationController.addStatusListener((status){
if (status == AnimationStatus.completed) {
setState(() {
// This is opposite, because it's implemented opposite in your code.
_visible = false;
});
}
});
I need shaking animation like this video
I'm newbie to Flutter. I would appreciate a solution or a link to the tutorial.
I think there can be better solution. But this one works fine, maybe it'll help
class TestAnimWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TestAnimWidgetState();
}
class _TestAnimWidgetState extends State<TestAnimWidget> with SingleTickerProviderStateMixin {
final TextEditingController textController = TextEditingController();
AnimationController controller;
#override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: 24.0).chain(CurveTween(curve: Curves.elasticIn)).animate(controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: offsetAnimation,
builder: (buildContext, child) {
if (offsetAnimation.value < 0.0) print('${offsetAnimation.value + 8.0}');
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.0),
padding: EdgeInsets.only(left: offsetAnimation.value + 24.0, right: 24.0 - offsetAnimation.value),
child: Center(child: TextField(controller: textController, )),
);
}),
RaisedButton(onPressed: () {
if (textController.value.text.isEmpty) controller.forward(from: 0.0);
},
child: Text('Enter'),)
],
),
);
}
}
I'd like to animate a gap between two items in a list. I thought of using an AminatedContainer with a height initially at zero but I'm not familiar with how to make this work. My code at the moment is:
new AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: App.itemSelected==id ? 50.0 : 0.0,
curve: Curves.fastOutSlowIn,
),
That does change the height of the Container but not in an animated way as I had hoped. Any help would be gratefully received!
I am not sure if AnimatedSize is suitable for your use case, but I have added an example on how to make a simple animation with it:
The coloring is a bit off due to the recording but you should be able to test this yourself.
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
double _height = 50.0;
double _width = 20.0;
var _color = Colors.blue;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new AnimatedSize(
curve: Curves.fastOutSlowIn, child: new Container(
width: _width,
height: _height,
color: _color,
), vsync: this, duration: new Duration(seconds: 2),),
new Divider(height: 35.0,),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.arrow_upward, color: Colors.green,),
onPressed: () =>
setState(() {
_color = Colors.green;
_height = 95.0;
})),
new IconButton(
icon: new Icon(Icons.arrow_forward, color: Colors.red,),
onPressed: () =>
setState(() {
_color = Colors.red;
_width = 45.0;
})),
],
)
],)
,)
);
}
}
You can use an AnimatedSize for that purpose.
https://api.flutter.dev/flutter/widgets/AnimatedSize-class.html
My goal is to create a clock similar to this. How can I achieve it using Flutter?
I would recommend the Layouts, Interactivity, and Animation tutorials. The codelab is also a good way to learn your way around Flutter.
Here's a sketch of how to build your app.
import 'dart:math' as math;
import 'package:meta/meta.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
theme: new ThemeData(
canvasColor: Colors.deepPurple,
iconTheme: new IconThemeData(color: Colors.white),
accentColor: Colors.pinkAccent,
brightness: Brightness.dark,
),
home: new MyHomePage(),
));
}
class ProgressPainter extends CustomPainter {
ProgressPainter({
#required this.animation,
#required this.backgroundColor,
#required this.color,
}) : super(repaint: animation);
/// Animation representing what we are painting
final Animation<double> animation;
/// The color in the background of the circle
final Color backgroundColor;
/// The foreground color used to indicate progress
final Color color;
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = backgroundColor
..strokeWidth = 5.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progressRadians = (1.0 - animation.value) * 2 * math.pi;
canvas.drawArc(
Offset.zero & size, math.pi * 1.5, -progressRadians, false, paint);
}
#override
bool shouldRepaint(ProgressPainter other) {
return animation.value != other.animation.value ||
color != other.color ||
backgroundColor != other.backgroundColor;
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
List<IconData> icons = <IconData>[
Icons.alarm, Icons.access_time, Icons.hourglass_empty, Icons.timer,
];
AnimationController _controller;
String get timeRemaining {
Duration duration = _controller.duration * _controller.value;
return '${duration.inMinutes} ${(duration.inSeconds % 60)
.toString()
.padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(seconds: 12),
)
..reverse(from: 0.4);
}
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return new Scaffold(
body: new Padding(
padding: const EdgeInsets.all(10.0),
child:
new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: icons.map((IconData iconData) {
return new Container(
margin: new EdgeInsets.all(10.0),
child: new IconButton(
icon: new Icon(iconData), onPressed: () {
// TODO: Implement
}),
);
}).toList(),
),
new Expanded(
child: new Align(
alignment: FractionalOffset.center,
child: new AspectRatio(
aspectRatio: 1.0,
child: new Stack(
children: <Widget>[
new Positioned.fill(
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new CustomPaint(
painter: new ProgressPainter(
animation: _controller,
color: themeData.indicatorColor,
backgroundColor: Colors.white,
),
);
}
),
),
new Align(
alignment: FractionalOffset.center,
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Text(
'Label', style: themeData.textTheme.subhead),
new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new Text(
timeRemaining,
style: themeData.textTheme.display4,
);
}
),
new Text('+1', style: themeData.textTheme.title),
],
),
),
],
),
),
),
),
new Container(
margin: new EdgeInsets.all(10.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new IconButton(icon: new Icon(Icons.delete), onPressed: () {
// TODO: Implement delete
}),
new FloatingActionButton(
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new Icon(
_controller.isAnimating
? Icons.pause
: Icons.play_arrow
);
},
),
onPressed: () {
if (_controller.isAnimating)
_controller.stop();
else {
_controller.reverse(
from: _controller.value == 0.0 ? 1.0 : _controller
.value,
);
}
},
),
new IconButton(
icon: new Icon(Icons.alarm_add), onPressed: () {
// TODO: Implement add time
}),
],
),
),
],
),
),
);
}
}