Flutter, how to automatically animate without a button trigger - flutter

I have some animation code that changes background color infinitely
but the problem is I want to do this without a button trigger
and I want this to be automated at first time (when it's firstly loading)
below is code
I`m so stuck could anyone help please
the purpose is to animate without a trigger, it automatically runs animation that changes background
import 'package:flutter/material.dart';
class ScreenTime extends StatefulWidget {
#override
_ScreenTimeState createState() => _ScreenTimeState();
}
class _ScreenTimeState extends State<ScreenTime> {
List<Color> colorList = [
Colors.red,
Colors.blue,
Colors.green,
Colors.yellow
];
List<Alignment> alignmentList = [
Alignment.bottomLeft,
Alignment.bottomRight,
Alignment.topRight,
Alignment.topLeft,
];
int index = 0;
Color bottomColor = Colors.black54;
Color topColor = Colors.white10;
Alignment begin = Alignment.bottomLeft;
Alignment end = Alignment.topRight;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
AnimatedContainer(
duration: Duration(seconds: 2),
onEnd: () {
setState(() {
index = index + 1;
// animate the color
bottomColor = colorList[index % colorList.length];
topColor = colorList[(index + 1) % colorList.length];
//// animate the alignment
// begin = alignmentList[index % alignmentList.length];
// end = alignmentList[(index + 2) % alignmentList.length];
});
},
decoration: BoxDecoration(
gradient: LinearGradient(
begin: begin, end: end, colors: [bottomColor, topColor])),
),
Positioned.fill(
child: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () {
setState(() {
bottomColor = Colors.blue;
});
},
),
)
],
));
}
}

To keep as much as your original implementation as possible while fixing the problem, doing
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {bottomColor = Colors.blue;}));
in initState would not pose an issue. This is as long as it's done in initState. Calling it in build would lead to an undesirable extra infinite loop.
However, using an AnimationController on repeat would almost certainly be a cleaner implmentation.

the purpose of this question was basically "how to animate AnimatedContainer without a trigger"
and I think I found a solution
what I did is
just make a new function and then in the build method, invokes the method
and method code is like below
setState(() {
index = index + 1;
bottomColor = Colors.blue;
});
my second solution is this and I think it works
but I worry if it has a any side effects
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));

Use this
bool selected = false;
#override
void initState() {
Future.delayed(const Duration(seconds: 1), () {
setState(() {
selected = true;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedAlign(
alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
duration: const Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 50.0),
),
],
));}
This Will automatically starts the animation. You can change the duration or animation type also.

Related

How to run fade transition animation from beginning every time in flutter?

In page view, there is a header that should be changed whenever the page is changed and there is a button to perform this operation. There is a list of texts named headers, and every time the button is pressed the text should be changed with another one with a fade transition animation. Now what I really want to achieve is that when the button is pressed the next text should replace the current text with 0 opacity and within a second its opacity should go up to 1 and it should be performed without any flaws and smoothly every time the page changes.
What I'm struggling with is that I can't set the animation value to 0 when the animation is completed without any side effects to the UI, it shouldn’t be noticeable.
In short, I want the animation value to start from zero every time the button is pressed.
You can watch how the animation is working via the link https://youtube.com/shorts/3aIAWjhZ2AM?feature=share.
The controller.forward(from: 0); is somehow working not as expected...
Here is my business logic:
class AnnouncementController extends ChangeNotifier {
late AnimationController controller;
late Tween<double> tween;
late Animation<double> opacityAnimation;
PageController pageController = PageController();
List<String> headers = [
'Welcome back, Jasurbek',
'What do you offer?',
'What kind of accommodation do you have?',
'Tell the guests about the advantages of your dwelling',
'Add photos of the property',
'Let\'s think of a bright title',
'Let\'s set the price',
];
navigate(int index){
currentPageIndex = index;
notifyListeners();
controller.forward(from: 0);
pageController.animateToPage(
index,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);
}
}
Here is my main class:
class _AnnouncementState extends State<Announcement> with SingleTickerProviderStateMixin {
final AnnouncementController viewModel = AnnouncementController();
#override
void initState() {
viewModel.controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
viewModel.tween = Tween(begin: 0.0, end: 1.0);
viewModel.opacityAnimation = viewModel.tween.animate(viewModel.controller);
viewModel.controller.forward();
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => viewModel,
child: Consumer<AnnouncementController>(builder: (ctx, model, index) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 250,
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(20),
child: FadeTransition(
opacity: viewModel.opacityAnimation,
child: Text(
viewModel.headers[viewModel.currentPageIndex],
style: TextStyle(
color: Colors.black,
fontSize: 26.sp,
fontWeight: FontWeight.bold,
),
),
)
),
Expanded(
child: PageView(
controller: viewModel.pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
FirstPage(viewModel: viewModel),
SecondPage(viewModel: viewModel),
ThirdPage(viewModel: viewModel),
FourthPage(viewModel: viewModel),
FifthPage(viewModel: viewModel),
],
onPageChanged: (int index) {
viewModel.navigate(index);
},
),
),
],
),
);
}),
);
}
}
Showcase
https://youtube.com/shorts/3aIAWjhZ2AM?feature=share
Please use animated container and you can add your animation in that and put your view inside as child
Document link- https://docs.flutter.dev/cookbook/animation/animated-container

Flutter - how to add animation to a Widget so that it slides into the view when a button is pressed?

Let's make a simple example, given a Column(), I have 2 Containers in it, and a button.
Column(
children: [
MyButton(
label: "Expand me"
onTap: () => setState(() => isOpen = !isOpen)
),
Container(
child: Text("Container 1"),
height: 200
),
if (isOpen)
Container(
child: Text("Container 2"),
height: 150
)
]
)
so basically, if we press the button, the second Container will appear right under the first one, like an expansion panel.
Now I want to add an animation, and I'm having a hard time finding the best fit for my use case, as most solutions look really complex for such a simple task.
The animation is really simple, instead of making the Container 2 appear out of nowhere under the first one, it would be nice if the Container 2 would start behind Container 1, and then slide towards the bottom, until in position.
What is the cleanest way to achieve this in flutter?
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class EasyAnimatedOffset extends StatefulWidget {
EasyAnimatedOffset();
#override
_EasyAnimatedOffsetState createState() => _EasyAnimatedOffsetState();
}
class _EasyAnimatedOffsetState extends State<EasyAnimatedOffset>
with SingleTickerProviderStateMixin {
//Notice the "SingleTickerProviderStateMixin" above
//Must add "AnimationController"
late AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
//change the animation duration for a slower or faster animation.
//For example, replacing 1000 with 5000 will give you a 5x slower animation.
duration: Duration(milliseconds: 1000),
);
}
animateForward() {
_animationController.forward();
//this controller will move the animation forward
//you can also create a reverse animation using "_animationController.reverse()"
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//the offset has a x value and y value.
//changing the y axis value moves the animation vertically
//changing the x axis value moves the animation horizantaly
double xAxisValue = 0;
double yAxisValue = 10;
return AnimatedBuilder(
animation: _animationController,
// child: widget.child,
builder: (context, child) {
return Transform.translate(
offset: Offset(_animationController.value * xAxisValue,
_animationController.value * yAxisValue),
//add your button or widget here
child: InkWell(
onTap: () {
animateForward();
},
child: Center(
child: Container(
height: 100,
width: 200,
color: Colors.amber,
child: Center(
child: Text(
"Animate Me",
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
)),
)));
});
}
}

setState is not updating UI in Flutter

I have a dialog box which is a stateful widget with multiple tabs wrapped inside Animated Switcher.
Inside it I have a button which on clicked calls a function switchPage() which has a switch statement with each case setting the state of Widget? currentTab variable to a different one.
The problem here arrives when I use this switchPage() function to change the value of currentTab to a different widget also wrapped in a different method getWidget2()
The code for the
Example on DartPad
Try clicking as I suggest...
Click Floating Button.
Click on the first checkbox.
Now click on PAGE 2 button.
Click the second checkbox only once. Now notice the checkbox doesn't work when clicked.
Click PAGE 1 again to go the working checkbox.
Now click on PAGE 2 button again. The Checkbox value and state did change but did not update the time it was clicked, but it has updated when we forcefully re visited the checkbox.
I cannot find solution to this anywhere and I really need the code structure to be as optimized as possible.
Please, if anyone has any explanation or any suggestions, it would be greatly appreciated..
Thanks, in advance.
This is a very rough working example, but you can build upon it.
In the code below, you can see that I have created two methods that handle setting the state of the checkbox for each widget. I have also reset the page once this method is triggered. What this does, is trigger the redrawing of the inner widgets (which is what I explained in my comment).
class _DemoDialogState extends State<DemoDialog> {
Widget? currentTab;
bool valueOfCheckbox1 = false;
bool valueOfCheckbox2 = false;
void switchPage(name) {
switch (name) {
case 1:
setState(() {
currentTab = getWidget1(setCheckbox1State); // <---- Notice the change here
});
break;
case 2:
setState(() {
currentTab = getWidget2(setCheckbox2State); // <---- Notice the change here
});
break;
}
}
void setCheckbox1State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox1 = newState;
currentTab = getWidget1(setCheckbox1State);
});
}
}
void setCheckbox2State(bool? newState) {
if (newState != null) {
setState(() { // <---- Notice the change here
valueOfCheckbox2 = newState;
currentTab = getWidget2(setCheckbox2State);
});
}
}
Widget getWidget1(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 1'),
Checkbox(
value: valueOfCheckbox1,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
Widget getWidget2(Function(bool?) checkboxFunction) {
return Container(
child: Row(
children: [
Text('Hello from widget 2'),
Checkbox(
value: valueOfCheckbox2,
onChanged: (value) { // <---- Notice the change here
checkboxFunction(value);
})
],
));
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 280,
height: 600,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 250),
reverseDuration: Duration(milliseconds: 250),
transitionBuilder: (child, animation) {
var begin = Offset(0.5, 0);
var end = Offset.zero;
var curve = Curves.easeIn;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
var begin2 = 0.0;
var end2 = 1.0;
var curve2 = Curves.easeIn;
var tween2 = Tween(begin: begin2, end: end2)
.chain(CurveTween(curve: curve2));
return SlideTransition(
position: animation.drive(tween),
child: FadeTransition(
opacity: animation.drive(tween2), child: child),
);
},
layoutBuilder: (widget, list) {
return Align(
alignment: Alignment.topCenter,
child: widget,
);
}, // <---- Notice the change here
child: currentTab == null ? getWidget1(setCheckbox1State) : currentTab,
),
TextButton(
onPressed: () {
switchPage(1);
},
child: Text('PAGE 1')),
TextButton(
onPressed: () {
switchPage(2);
},
child: Text('PAGE 2'))
],
),
),
);
}
}
This is just an example that makes things work, but it in no way represents how you should build things appropriately. I would look into separating the code into stateless and stateful widgets.

How to restart an animation stopped before completion but with different parameters

I have an animation where two containers collide with each other. I want the containers to reverse back with animation to their respective starting offset. The containers move along varied paths and duration.
As seen in the animation gif attached the green and red containers collide and jump back to their starting offsets rather than sliding back.
Here is the code I used to make the GIF, in my actual code I'm using rectangle intersection to check when the containers collide.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
List<AnimationController> _controller = List(2);
List<Animation<Offset>> _animation = List(2);
List<Tween<Offset>> tween = List(2);
#override
void initState() {
super.initState();
tween[0] = Tween(begin: Offset(0, 10), end: Offset(200, 10));
tween[1] = Tween(begin: Offset(200, 10), end: Offset(0, 10));
for (int i = 0; i < 2; i++) {
_controller[i] =
AnimationController(vsync: this, duration: Duration(seconds: 1));
_animation[i] = tween[i].animate(_controller[i])
..addListener(
() {
setState(
() {
print(
'${_animation[0].value.dx.toInt()}:${_animation[0].value.dy.toInt()} ${_animation[1].value.dx.toInt()}:${_animation[1].value.dy.toInt()}');
if (((_animation[0].value.dx) / 2).round() ==
((_animation[1].value.dx) / 2).round()) {
_controller[0].stop();
_controller[1].stop();
_controller[0].reset();
_controller[1].reset();
Future.delayed(Duration(milliseconds: 100)).then((_) {
tween[0] = Tween(
begin: Offset(
_animation[0].value.dx, _animation[0].value.dy),
end: Offset(0, 10));
tween[1] = Tween(
begin: Offset(
_animation[1].value.dx, _animation[1].value.dy),
end: Offset(200, 10));
_animation[0] = tween[0].animate(_controller[0]);
_animation[1] = tween[1].animate(_controller[1]);
_controller[0].forward();
_controller[1].forward();
});
}
},
);
},
);
}
WidgetsBinding.instance.addPostFrameCallback(
(_) => _afterLayout(),
);
}
void _afterLayout() {
for (int i = 0; i < 2; i++) {
_controller[i].forward();
}
}
#override
void dispose() {
super.dispose();
for (int i = 0; i < 1; i++) {
_controller[i].dispose();
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("collision"),
),
body: Column(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
),
Positioned(
left: _animation[0] != null ? _animation[0].value.dx : 0,
top: _animation[0] != null ? _animation[0].value.dy : 0,
child: Container(
width: 50,
height: 50,
color: Colors.red,
),
),
Positioned(
left: _animation[1] != null ? _animation[1].value.dx : 0,
top: _animation[1] != null ? _animation[1].value.dy : 0,
child: Container(
width: 50,
height: 50,
color: Colors.green,
),
),
],
),
),
RaisedButton(
onPressed: () {
_controller[0].reset();
_controller[1].reset();
tween[0] = Tween(begin: Offset(0, 10), end: Offset(200, 10));
tween[1] = Tween(begin: Offset(200, 10), end: Offset(0, 10));
_controller[0].duration = Duration(seconds: 1);
_controller[1].duration = Duration(seconds: 1);
_animation[0] = tween[0].animate(_controller[0]);
_animation[1] = tween[1].animate(_controller[1]);
_controller[0].forward();
_controller[1].forward();
},
child: Text("Animate"),
)
],
),
),
);
}
}
I want the containers to smoothly slide back to their respective starting offsets with animation.
I will add a bit of explanation here to help others maybe.
Animations in flutter are controlled by an AnimationController. You can control one ore more animations using a single AnimationController ( you can use same controller if you want to have animations triggered simultaneously).
Now, to start an animation (or all animations linked to the same controller) you can use forward() method. The forward() method accepts a parameter that specify from which point on should the animation continue, the parameter value is between 0...1.
If you want to do the animation in backwards, the controller has a method called reverse() which will perform the animation in the reverse order. Same, the method accepts a parameter with value from 0 to 1, if no parameter is specified the animation will "reverse" from the last state.
Note in order to have a reverse animation you should not call reset() on the controller. reset() method will set all the values of the controller to the initial ones.

Flutter: inverted ClipOval

I am new to Flutter and I am trying to write a library to allow users to pan/zoom their profile picture.
In order to make it visual, I would like to stack their picture with an "inverted" ClipOval, to show the boundaries.
So far, this is the result I obtain:
This shows the boundaries but this is not user friendly and I would like to "invert" the ClipOval so that the center of the clip is "clear" and the outside is grayed out (something like a mask).
Is there any way to achieve this?
Here is the code I have so far (part of it comes from flutter_zoomable_image):
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ImagePanner extends StatefulWidget {
ImagePanner(this.image, {Key key}) : super(key: key);
/// The image to be panned
final ImageProvider image;
#override
_ImagePannerState createState() => new _ImagePannerState();
}
class _ImagePannerState extends State<ImagePanner> {
ImageStream _imageStream;
ui.Image _image;
double _zoom = 1.0;
Offset _offset = Offset.zero;
double _scale = 16.0;
#override
void didChangeDependencies() {
_resolveImage();
super.didChangeDependencies();
}
#override
void reassemble() {
_resolveImage();
super.reassemble();
}
#override
Widget build(BuildContext context) {
if (_image == null) {
return new Container();
}
return new Container(
width: double.INFINITY,
color: Colors.amber,
child: new Padding(
padding: new EdgeInsets.all(50.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new AspectRatio(
aspectRatio: 1.0,
child: new Stack(
children: [
_child(),
new Opacity(
opacity: 0.5,
child: new ClipOval(
child: new Container(
color: Colors.black,
),
),
),
],
),
),
],
)),
);
}
Widget _child() {
Widget bloated = new CustomPaint(
child: new Container(),
painter: new _ImagePainter(
image: _image,
offset: _offset,
zoom: _zoom / _scale,
),
);
bloated = new Stack(
children: [
new Container(
),
bloated
],
);
return new Transform(
transform: new Matrix4.diagonal3Values(_scale, _scale, _scale),
child: bloated);
}
void _resolveImage() {
_imageStream = widget.image.resolve(createLocalImageConfiguration(context));
_imageStream.addListener(_handleImageLoaded);
}
void _handleImageLoaded(ImageInfo info, bool synchronousCall) {
print("image loaded: $info $synchronousCall");
setState(() {
_image = info.image;
});
}
}
class _ImagePainter extends CustomPainter {
const _ImagePainter({this.image, this.offset, this.zoom});
final ui.Image image;
final Offset offset;
final double zoom;
#override
void paint(Canvas canvas, Size size) {
paintImage(canvas: canvas, rect: offset & (size * zoom), image: image);
}
#override
bool shouldRepaint(_ImagePainter old) {
return old.image != image || old.offset != offset || old.zoom != zoom;
}
}
The outcome I would like to obtain is the following so that users will directly see the boundaries and will be able to center, pan, zoom their profile picture INSIDE the oval.
(I made this via Photoshop, since I don't know how to achieve this with Flutter)
Many thanks for your help.
There's a couple other ways you could do this - you could simply draw an overlay in a CustomCanvas using a path that has a circle & rectangle, as all you really need is a rectangular semi-transparent rectangle with a hole in it. But you can also use a CustomClipper which gives you more flexibility in the future without having to draw stuff manually.
void main() {
int i = 0;
runApp(new MaterialApp(
home: new SafeArea(
child: new Stack(
children: <Widget>[
new GestureDetector(
onTap: () {
print("Tapped! ${i++}");
},
child: new Container(
color: Colors.white,
child: new Center(
child: new Container(
width: 400.0,
height: 300.0,
color: Colors.red.shade100,
),
),
),
),
new IgnorePointer(
child: new ClipPath(
clipper: new InvertedCircleClipper(),
child: new Container(
color: new Color.fromRGBO(0, 0, 0, 0.5),
),
),
)
],
),
),
));
}
class InvertedCircleClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
return new Path()
..addOval(new Rect.fromCircle(
center: new Offset(size.width / 2, size.height / 2),
radius: size.width * 0.45))
..addRect(new Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
IgnorePointer is needed, or events won't be propagated through the semi-transparent part (assuming you need touch events).
How this works is that the Path used by clipPath is a circle in the middle (you need to adjust the size manually) with a rectangle taking up the entire size. fillType = PathFillType.evenOdd is important because it tells the path's fill should be between the circle and the rectangle.
If you wanted to use a customPainter instead, the path would be the same and you'd just draw it instead.
This all results in this: