I'd like to be able to transition a TextField to (and from) just the Text, which means the input decorations fading out, and the text itself moving slightly to the left (as the input padding goes to 0).
I had some limited success with the following code, using the intrinsic animations of TextField. However, the text jumps left instead of moving there gradually, and the input decorations jump in/out as well as fading.
class ExampleScreen extends StatefulWidget {
#override
ExampleScreenState createState() => ExampleScreenState();
}
class ExampleScreenState extends State<ExampleScreen> {
TextEditingController text = TextEditingController();
bool editing = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: EditableText(controller: text, editing: editing),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => editing = !editing),
child: Icon(Icons.ac_unit),
),
);
}
}
class EditableText extends StatelessWidget {
final TextEditingController controller;
final bool editing;
EditableText({this.controller, this.editing});
#override Widget build(BuildContext context) {
return TextField(
controller: controller,
enabled: editing,
decoration: InputDecoration().copyWith(
contentPadding: editing ? Theme.of(context).inputDecorationTheme.contentPadding : EdgeInsets.zero,
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent))
),
);
}
}
Any clever ideas?
I wrote a working example, but there are still some flicker, you can try some [curve] to modify the animation.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: AnimatedTextField(
controller: controller,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.opacity),
onPressed: () {
var status = controller.status;
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
},
),
);
}
}
class AnimatedTextField extends StatefulWidget {
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> left;
AnimatedTextField({
Key key,
#required this.controller,
}) : opacity = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.5,
curve: Curves.easeInOut,
),
),
),
left = Tween<double>(
begin: 20.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
1.0,
curve: Curves.easeIn,
),
),
),
super(key: key);
#override
_AnimatedTextFieldState createState() => _AnimatedTextFieldState();
}
class _AnimatedTextFieldState extends State<AnimatedTextField> {
TextEditingController _textEditingController =
TextEditingController(text: 'hello fluttr');
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.controller,
builder: (context, child) {
var theme = Theme.of(context);
var o = widget.opacity.value;
var _child = o > 0.0
? TextField(
controller: _textEditingController,
style: theme.textTheme.body2,
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(0, 0, 0, 1)),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(0, 0, 0, o)),
),
),
)
: Opacity(
opacity: (o - 1).abs(),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
_textEditingController.text,
style: theme.textTheme.body2,
),
),
);
return Padding(
padding: EdgeInsets.only(
left: widget.left.value,
top: 20,
right: 20,
bottom: 20,
),
child: _child,
);
},
);
}
}
Managed to work it out. Created an 'AnimatedDouble' widget which just makes a builder with the value of an animation as a parameter, triggered by a ChangeNotifier:
class AnimatedDouble extends StatefulWidget {
final Widget child;
final Function(BuildContext context, Widget child, double d) builder;
final Duration duration;
final ChangeNotifier listen;
AnimatedDouble({this.child, this.builder, this.duration, this.listen});
#override
_AnimatedDoubleState createState() => new _AnimatedDoubleState();
}
class _AnimatedDoubleState extends State<AnimatedDouble> with SingleTickerProviderStateMixin{
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(value: 1.0, vsync: this, duration: widget.duration);
widget.listen.addListener(_go);
}
#override
void dispose() {
controller.dispose();
widget.listen.removeListener(_go);
super.dispose();
}
void _go() {
controller.forward(from: 0.0);
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
child: widget.child,
builder: (context, child) => widget.builder(context, child, controller.view.value),
);
}
}
Then was able to use it like so to get the effect I wanted:
AnimatedDouble(
duration: Duration(milliseconds: 200),
listen: something_to_trigger_animation,
builder: (context, child, d) => TextField(
controller: controller,
decoration: InputDecoration(
contentPadding: editing ?
EdgeInsets.symmetric(vertical: 16.0 * d, horizontal: 12.0 * d) :
EdgeInsets.symmetric(vertical: 16.0 * (1 - d), horizontal: 12.0 * (1 - d)),
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)),
),
enabled: editing,
),
),
Related
I want to make somthing like this in flutter
a blue iamge above a text but it's displied part by part ,how I can made somthing like this or if there is any good libery for that I on ready google it but I didnt find what excatly what I want
I try this but it's not what I want excatly
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "App",
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double rightValue = 1000;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Center(
child: Stack(
alignment: Alignment.center,
children: [
AnimatedPositioned(
duration: Duration(seconds: 2),
right: rightValue,
child: Image.asset('assets/images/bg.png'),
),
Center(
child: Text('Hello world',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold)),
),
//),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
rightValue = 0;
});
},
),
),
);
}
}
the idea is using CustomClipper, animate it to reveal image :
EDIT -- as of yeasin suggestion, you can animate the text with color tween, warp it in stack.
here edited result :
code :
class Testing extends StatefulWidget {
const Testing({Key? key}) : super(key: key);
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
late final Animation<double> _animation;
late final Animation<Color?> _animationColor;
#override
void initState() {
// TODO: implement initState
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 1200))
..repeat(reverse: false);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.ease));
_animationColor = ColorTween(begin: Colors.black, end: Colors.white)
.animate(
CurvedAnimation(parent: _animationController, curve: Curves.ease));
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Center(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return ClipPath(
clipper: MyClipper(anim: _animation.value),
child: Container(
height: 300.0,
width: 300.0,
color: Colors.blue,
),
);
},
),
),
AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return Center(
child: Text(
"your image",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: _animationColor.value),
));
},
),
],
),
);
}
}
class MyClipper extends CustomClipper<Path> {
final double anim;
MyClipper({required this.anim});
#override
getClip(Size size) {
// TODO: implement getClip
var rect = Rect.fromLTWH(0.0, 0.0, size.width * anim, size.height);
var path = Path();
path.addRect(rect);
return path;
}
#override
bool shouldReclip(covariant MyClipper oldClipper) {
return oldClipper != this;
}
}
Trying to get desired output of the Animation above. Tried with AnimatedBuilder along with Transform of Matrix of z-axis for this animation. But failed to do so. Here's my code. I couldn't get the angle, shrinking or the shaking part right.
MyHomePage
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late TabController _tabController;
late void Function() _memberCaller;
late void Function() _voucherCaller;
late void Function() _rewardCaller;
late void Function() _drinksCaller;
double degree = 0;
#override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _callMethoCaller(int index) {
switch (index) {
case 0:
_memberCaller.call();
break;
case 1:
_voucherCaller.call();
break;
case 2:
_rewardCaller.call();
break;
case 3:
_drinksCaller.call();
break;
}
}
#override
Widget build(BuildContext context) {
List<Widget> _tabs(int index) => [
AnimationTab(
iconName: Icons.card_giftcard,
label: 'Membership',
functionCaller: (void Function() method) {
_memberCaller = method;
}),
AnimationTab(
iconName: Icons.confirmation_num_outlined,
label: 'Voucher',
functionCaller: (void Function() method) {
_voucherCaller = method;
}),
AnimationTab(
iconName: Icons.emoji_events_outlined,
label: 'Rewards',
functionCaller: (void Function() method) {
_rewardCaller = method;
}),
AnimationTab(
iconName: Icons.wine_bar_outlined,
label: 'Drinks',
functionCaller: (void Function() method) {
_drinksCaller = method;
}),
];
return Scaffold(
appBar: AppBar(
centerTitle: true,
bottom: PreferredSize(
child: Container(
child: TabBar(
onTap: _callMethoCaller,
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.black,
tabs: List.generate(4, (index) => _tabs(index)[index]),
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.white,
spreadRadius: 5.0,
offset: Offset(0, 3))
],
),
),
preferredSize: Size.fromHeight(30),
),
),
body: TabBarView(
controller: _tabController,
children: const [
Center(child: Text('1')),
Center(child: Text('2')),
Center(child: Text('3')),
Center(child: Text('4')),
],
),
);
}
}
AnimationTab
class AnimationTab extends StatefulWidget {
final String label;
final IconData iconName;
final FunctionCaller functionCaller;
const AnimationTab({
Key? key,
required this.iconName,
required this.label,
required this.functionCaller,
}) : super(key: key);
#override
_AnimationTabState createState() => _AnimationTabState();
}
class _AnimationTabState extends State<AnimationTab>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
final _curvedAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.bounceIn,
reverseCurve: Curves.bounceOut);
_animation = TweenSequence<double>([
TweenSequenceItem<double>(tween: Tween(begin: 0, end: 12.5), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 12.5, end: 0), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 0, end: -12.5), weight: 1),
]).animate(_curvedAnimation)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
}
});
}
void _animationExecution() {
_animationController.forward();
}
#override
Widget build(BuildContext context) {
const _tabTextStyle = TextStyle(
fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black);
widget.functionCaller.call(_animationExecution);
return AnimatedBuilder(
animation: _animationController,
builder: (ctx, _) {
return Transform(
transform: Matrix4.rotationZ(math.pi * _animation.value / 180),
alignment: Alignment.center,
child: Tab(
icon: Icon(widget.iconName, color: Colors.black),
child: Text(widget.label, style: _tabTextStyle),
),
);
},
);
}
}
you dont need those TweenSequences, "status listeners" etc, also instead of Matrix4.rotationZ use ordinary Transform.rotate, check this:
class TabTest extends StatefulWidget {
#override
_TabTestState createState() => _TabTestState();
}
class _TabTestState extends State<TabTest> with TickerProviderStateMixin {
late TabController tabController;
late List<AnimationController> animationControllers;
#override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this)
..addListener(_listener);
animationControllers = List.generate(4, (i) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
reverseDuration: Duration(milliseconds: 350),
));
}
#override
Widget build(BuildContext context) {
// timeDilation = 5;
return Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: List.generate(4, (i) => AnimatedBuilder(
animation: animationControllers[i],
builder: (context, child) {
final child = Tab(
icon: Icon(Icons.cast),
child: Text('tab $i'),
);
final value = animationControllers[i].value;
if (animationControllers[i].status == AnimationStatus.forward) {
final angle = sin(4 * pi * value) * pi * 0.3;
return Transform.rotate(angle: angle, child: child);
} else {
final dy = sin(2 * pi * value) * 0.2;
return FractionalTranslation(translation: Offset(0, dy), child: child);
}
},
),
),
controller: tabController,
),
),
body: TabBarView(
children: List.generate(4, (i) =>
FittedBox(
child: Text('tab $i'),
),
),
controller: tabController,
),
);
}
void _listener() {
if (tabController.indexIsChanging) {
animationControllers[tabController.previousIndex].reverse();
} else {
animationControllers[tabController.index].forward();
}
}
#override
void dispose() {
super.dispose();
tabController
..removeListener(_listener)
..dispose();
animationControllers.forEach((ac) => ac.dispose());
}
}
I have an ExpansionTile that have different titles in expanded\collapsed state.
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Expanded(
child: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: _myAnimatedWidget,
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
),
children: widget.content,
),
);
}
}
I want to make an animation between these states, how I can achieve it?
I tried AnimatedSwitcher, but it didn't work. I'm totally don't see an animation.
You can copy paste run full code below
You can wrap _myAnimatedWidget with Container and provide key: ValueKey<bool>(isExpanded)
From official example https://api.flutter.dev/flutter/widgets/AnimatedSwitcher-class.html
This key causes the AnimatedSwitcher to interpret this as a "new"
child each time the count changes, so that it will begin its animation
when the count changes.
I also remove Expanded in title
code snippet
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
working demo
full code
import 'package:flutter/material.dart';
class ExpandablePane extends StatefulWidget {
Widget expandedTitle;
Widget collapsedTitle;
List<Widget> content;
ExpandablePane({this.expandedTitle, this.collapsedTitle, this.content});
#override
_ExpandablePaneState createState() => _ExpandablePaneState();
}
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
children: widget.content,
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ExpandablePane(
expandedTitle: Text("expand"),
collapsedTitle: Text("collapsed"),
content: [Text("1"), Text("2"), Text("3")],
),
],
),
),
);
}
}
I'm trying to animate an alert dialog in flutter so that when it pop ups it shows an animation like this below.
How can I achieve following look and behaviour from Pokemon Go in an alertDialog?
I would really like to have this animation in my app.
Thanks for your Answers!
Try this, modify any variable to meet your requirement:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _animation;
double _width = 20;
double _height = 200;
Color _color = Colors.transparent;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation = Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, -2.0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInCubic,
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.send),
onPressed: () {
setState(() {
_color = Colors.white;
});
_controller.forward().then((_) {
_width = 200;
setState(() {});
});
},
),
],
),
body: Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: SlideTransition(
position: _animation,
child: AnimatedContainer(
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(10),
),
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
),
),
),
],
),
);
}
}
This is a widget for my quiz app. I am currently working on the quiz option button.
I want it to blink green or red whether the answer is true or not.
But it's not working. I tried different things but I couldn't succeed.
import 'package:flutter/material.dart';
class TestButton extends StatefulWidget {
TestButton({this.text, this.color, this.onPressed});
final Function onPressed;
final Color color;
final String text;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _animation;
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {
});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});
_animationController.forward();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animation.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
),
),
),
);
}
}
And this is the usage of the button. I am applying a setState function but it doesn't make sense. The problem is that the animation works but the setState function doesn't change the color value in the ColorTween
TestButton(
text: question.optionA,
color: colorA,//Colors.transparent
onPressed: () {
if (canTap) {
canTap = false;
if (question.answer == 1) {
setState(() {
colorA = Colors.green;
});
} else {
setState(() {
colorA = Colors.red;
});
}
}
},
),
Remove this part:
..addListener(() {
setState(() {
});
});
And use AnimatedBuilder like this:
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: AnimatedBuilder(
animation: _animationController,
builder: (_, __) => Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animationController.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
)
),
),
),
);
You can copy paste run full code below
You can use didUpdateWidget to reset _animation
In working demo, when click button, color changes from red to green
#override
void didUpdateWidget(covariant TestButton oldWidget) {
if (oldWidget.color != widget.color) {
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
}
super.didUpdateWidget(oldWidget);
}
working demo
full code
import 'package:flutter/material.dart';
class TestButton extends StatefulWidget {
TestButton({this.text, this.color, this.onPressed});
final Function onPressed;
final Color color;
final String text;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _animation;
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});
_animationController.forward();
}
#override
void didUpdateWidget(covariant TestButton oldWidget) {
if (oldWidget.color != widget.color) {
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animation.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
),
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Color colorA = Colors.red;
bool canTap = true;
int answer = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TestButton(
text: "question.optionA",
color: colorA, //Colors.transparent
onPressed: () {
if (canTap) {
canTap = false;
if (answer == 1) {
setState(() {
colorA = Colors.green;
});
} else {
setState(() {
colorA = Colors.red;
});
}
}
},
),
],
),
),
);
}
}