How to create a Slide-In and Slide-Out animation in Flutter? - animation

I am trying to create a Slide in and Slide out animation in Flutter. Animation should look like this:
----- Widget slides in ---> Wait for 1 seconds -----Widget slides out of screen -->
I have tried following code but my animation is stuck in a loop.
class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _positionAnimation;
Animation<double> opacityAnimation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_positionAnimation = Tween<Offset>(
begin: const Offset(-1, 0),
end: const Offset(0, 0.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut),
)..addStatusListener((status) {
print('animation 1 status $status');
if (status == AnimationStatus.completed) {
_controller.reset();
}
if (status == AnimationStatus.dismissed) {
_positionAnimation = Tween<Offset>(
begin: const Offset(0, 0),
end: const Offset(1, 0.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.elasticIn),
)..addStatusListener((status2) {
print('animation 2 status $status2');
if (status == AnimationStatus.dismissed) {
_controller.reset();
}
});
_controller.forward();
}
});
_controller.forward();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: _controller,
);
}
Widget _buildAnimation(BuildContext context, Widget child) {
return Opacity(
opacity: 1,
child: SlideTransition(
position: _positionAnimation,
child: Container(
color: Colors.blueAccent,
height: 100,
child: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.display1,
),
),
),
),
);
}
With this approach animation is stuck in a loop.
I cannot use staggered animation as I am trying to animate same property. (Or is there a way to use staggered animation on same property?).
Any better way for implementing this?

Taking idea from #pskink code in comments, achieved desired effect with following code using TweenAnimationBuilder:
class _SlideInOutWidgetState extends State<SlideInOutWidget>
with SingleTickerProviderStateMixin {
double startPos = -1.0;
double endPos = 0.0;
Curve curve = Curves.elasticOut;
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<Offset>(begin: Offset(startPos, 0), end: Offset(endPos, 0)),
duration: Duration(seconds: 1),
curve: curve,
builder: (context, offset, child) {
return FractionalTranslation(
translation: offset,
child: Container(
width: double.infinity,
child: Center(
child: child,
),
),
);
},
child: Text('animated text', textScaleFactor: 3.0,),
onEnd: () {
print('onEnd');
Future.delayed(
Duration(milliseconds: 500),
() {
curve = curve == Curves.elasticOut
? Curves.elasticIn
: Curves.elasticOut;
if (startPos == -1) {
setState(() {
startPos = 0.0;
endPos = 1.0;
});
}
},
);
},
);
}
}

Use Marquee plugin for text Animations
Install :
marquee: ^1.3.1
Example :
Marquee(
text: 'There once was a boy who told this story about a boy: "',
)
Fore more info try marquee | flutterpckage

Related

How to scroll or jump to position of PageView.builder or PageController in Flutter?

Issue: Unable to scroll to POSITION after loading the Pageviews using PageController *
like ViewPager scroll to specific page in Android
Widget _buildCarousel(BuildContext context, int selectedIndex) {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
// you may want to use an aspect ratio here for tablet support
height: 400.0,
width: 240,
child: PageView.builder(
itemCount: assetImageList.length,
controller: controller,
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
)
],
);
}
Finally found the answer. Just set the initialPage: mSelectedPosition attribute like:
child: PageView.builder(
itemCount: mTemplateModelList.length,
controller: PageController(initialPage: mSelectedPosition, keepPage: true, viewportFraction: 1),
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
OR if you want to scroll the page after the button is clicked then, you can use jumpTo() method using PageController which is clearly mentioned below by another user: #android.
Currently there's 2 options to handle your request:
PageView.builder(
controller: _pageController,
itemCount: _list.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_pageController.jumpToPage(index); // for regular jump
_pageController.animateToPage(_position, curve: Curves.decelerate, duration: Duration(milliseconds: 300)); // for animated jump. Requires a curve and a duration
},
child: Container();
);
}
),
You can use jumpTo() method to scroll position for PageView.
I have create one changePageViewPostion() method in below example:
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
createState() {
return StateKeeper();
}
}
class StateKeeper extends State<MyPageView> {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
var currentPageValue = 0.0;
var mItemCount = 10;
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
setState(() {
currentPageValue = controller.page;
});
});
}
void changePageViewPostion(int whichPage) {
if(controller != null){
whichPage = whichPage + 1; // because position will start from 0
double jumpPosition = MediaQuery.of(context).size.width / 2;
double orgPosition = MediaQuery.of(context).size.width / 2;
for(int i=0; i<mItemCount; i++){
controller.jumpTo(jumpPosition);
if(i==whichPage){
break;
}
jumpPosition = jumpPosition + orgPosition;
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PageView position change'),
),
body: PageView.builder(
controller: controller,
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.blue : Colors.pink,
child: Column(
children: <Widget>[
Center(
child: Text(
"Page " + (position + 1).toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(padding: EdgeInsets.only(bottom: 20),
child: FloatingActionButton(
elevation: 0.0,
child: new Icon(Icons.check),
backgroundColor: new Color(0xFFE57373),
onPressed: (){
changePageViewPostion(5);
}
),),
),
],
),
);
},
itemCount: mItemCount,
)
);
}
}
We can get current position with controller as below:
controller.addListener(() {
setState(() {
currentPageValue = controller.page.toInt();
print((currentPageValue + 1).toString());
});
});
Hope it helps :)
If you just want to scroll to the next page using a button, you can simply use the following method.
//Create a PageController variable
late PageController _pageController;
//Initialize the variable in the init method.
#override
void initState() {
_pageController = PageController(
initialPage: _activePage, keepPage: true, viewportFraction: 1);
super.initState();
}
//Use this nextPage() method in the onPressed() method.
onPressed: () {
setState(() {
_activePage < 2
? _activePage++
: Navigator.pushReplacementNamed(
context, LoginScreen.id);
});
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}

Animated FloatingActionButton in Stack don't trigger onPressed event

I've created an animated Floating Action Button (FAB) menu:
Main FAB which spawns other FABs when clicked.
It is based on several online tutorials.
Works fine besides one major issue:
The spawned FABs don't trigger an onPressed event.
Seems like there's a problem with the combination of transform animation and the stack widget (which the FABs are children of).
Works fine if I replace the stack with a row...
Seems like the issue was addressed here, but without a proper solution:
FloatingActionButton onPressed not triggering
Here's the complete code for that FAB menu.
Just supply it a list with buttons data, similar to this one:
List<Map<String, dynamic>> _buttonsData = [
{'color': Colors.green ,'icon': Icons.stop},
{'color': Colors.blue ,'icon': Icons.subway},
{'color': Colors.green ,'icon': Icons.add},];
FancyFab2(_buttonsData, null)
The code:
import 'package:flutter/material.dart';
class FancyFab extends StatefulWidget {
FancyFab(this._buttonsData, this._onSelected);
final List<Map<String, dynamic>> _buttonsData;
final ValueChanged<int> _onSelected;
#override
createState() => FancyFabState(_buttonsData, _onSelected);
}
class FancyFabState extends State<FancyFab> with SingleTickerProviderStateMixin {
final List<Map<String, dynamic>> _buttonsData;
final ValueChanged<int> _onSelected;
// state vars
AnimationController _controller;
Animation <double> _transform;
bool _isOpened = false;
FancyFabState(this._buttonsData, this._onSelected);
#override
void initState() {
// call base
super.initState();
// _controller
_controller = AnimationController(
duration: Duration(milliseconds: 100),
vsync: this
);
_transform = Tween<double>(
begin: 0.0,
end: -64.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn
),
);
}
#override
void dispose() {
// controller
_controller.dispose();
// call base
super.dispose();
}
#override
Widget build(BuildContext context) {
return _buildFab(_controller);
}
Widget _buildFab(AnimationController controller) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Stack(
children: List.generate(_buttonsData.length, (index) => _buildStackWidget(context, index))
);
}
);
}
Widget _buildStackWidget(BuildContext context, int index) {
Map<String, dynamic> buttonData = _buttonsData[index];
if (index == _buttonsData.length - 1)
return _buildMenuButton(index, buttonData);
else
return _buildMenuItem(index, buttonData);
}
Widget _buildMenuItem(int index, Map<String, dynamic> buttonData) {
return Transform.translate(
offset: Offset((1 + index) * _transform.value, 0.0),
child: FloatingActionButton(
heroTag: 100 + index,
backgroundColor: buttonData['color'],
//onPressed: () => _onSelected(index),
onPressed: () => print('click'),
child: Icon(buttonData['icon']),
),
);
}
Widget _buildMenuButton(int index, Map<String, dynamic> buttonData) {
return FloatingActionButton(
heroTag: 200,
backgroundColor: buttonData['color'],
onPressed: _toggle,
child: Icon(buttonData['icon']),
);
}
void _toggle() {
print('toggle');
_isOpened = !_isOpened;
if (true == _isOpened)
_controller.forward();
else
_controller.reverse();
}
}
That's because you are moving the items outside your Stack so you can't hitTest on those positions.
I modified a few lines of your code to make it works :
Add constraints to your Stack (you can use SizedBox also) , I'm using container to set a different color of background.
Widget _buildFab(AnimationController controller) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Container(
width: MediaQuery.of(context).size.width,
height: 100,
color: Colors.blueGrey,
child: Stack(
children: List.generate(_buttonsData.length,
(index) => _buildStackWidget(context, index))),
);
});
}
Center your items
Widget _buildMenuItem(int index, Map<String, dynamic> buttonData) {
return Center(
child: Transform.translate(
offset: Offset((1 + index) * _transform.value, 0.0),
child: FloatingActionButton(
heroTag: 100 + index,
backgroundColor: buttonData['color'],
//onPressed: () => _onSelected(index),
onPressed: () => print('click'),
child: Icon(buttonData['icon']),
),
),
);
}
Widget _buildMenuButton(int index, Map<String, dynamic> buttonData) {
return Center(
child: FloatingActionButton(
heroTag: 200,
backgroundColor: buttonData['color'],
onPressed: _toggle,
child: Icon(buttonData['icon']),
),
);
}

Difference between Animation Tween and Animation Controlller

In some Flutter animation tutorials, some uses a Tween and an Animation object. Some uses AnimationController only. Both code below seems to output the same result. So when do we use a Tween with animation and when do we use AnimationController only?
With Tween and animation
import 'dart:core';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
#override
_State createState() {
return _State();
}
}
class _State extends State<Test> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _animation;
bool faded = true;
#override
void initState() {
super.initState();
_animationController = new AnimationController(
value:0.0,
vsync: this,
upperBound: 1.0,
lowerBound: 0.0,
duration: new Duration(seconds:1),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
elevation: 0.5,
title: new Text(
"Testing views",
style: Theme.of(context).textTheme.title,
),
),
body: _buildBodyAnimationTest(),
// body: _buildTuto(),
);
}
Widget _buildBodyAnimationTest(){
return FadeTransition(
opacity: _animation, //here is the difference
child: InkWell(
onTap: (){
if(faded){
faded = false;
_animationController.reverse();
}else{
faded = true;
_animationController.forward();
}
},
child: new Container(
color: Colors.red,
),
),
);
}
}
Without Tween and Animation
import 'dart:core';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
#override
_State createState() {
return _State();
}
}
class _State extends State<Test> with TickerProviderStateMixin {
AnimationController _animationController;
bool faded = true;
#override
void initState() {
super.initState();
_animationController = new AnimationController(
value:0.0,
vsync: this,
upperBound: 1.0,
lowerBound: 0.0,
duration: new Duration(seconds:1),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
elevation: 0.5,
title: new Text(
"Testing views",
style: Theme.of(context).textTheme.title,
),
),
body: _buildBodyAnimationTest(),
// body: _buildTuto(),
);
}
Widget _buildBodyAnimationTest(){
return FadeTransition(
opacity: _animationController, //here is the difference
child: InkWell(
onTap: (){
if(faded){
faded = false;
_animationController.reverse();
}else{
faded = true;
_animationController.forward();
}
},
child: new Container(
color: Colors.red,
),
),
);
}
}
Tweens are objects used to transform the output of an Animation (such as AnimationController).
With AnimationController, you typically have a 0-1 floating value. But you rarely want that.
Tweens allows to map that 0-1 to something more concrete such as red to blue, left to right, ...
Background is my tween sequence color.
Animatable<Color> background = TweenSequence<Color>(
[
TweenSequenceItem(
weight: 1.0,
tween: ColorTween(
begin: colors[_Substance.dayEndBackground],
end: colors[_Substance.dayStartBackground],
),
),
TweenSequenceItem(
weight: 1.0,
tween: ColorTween(
begin: colors[_Substance.dayStartBackground],
end: colors[_Substance.dayEndBackground],
),
),
],
);
This is my controller defined in initState() and updated in every one second:
bgUpdateController = AnimationController(
value: _currentDateTime.hour / 24,
upperBound: 1,
lowerBound: 0,
duration: const Duration(hours: 24),
vsync: this,
)..repeat();
I have used the above background and controller as AnimatedBuilder like below:
AnimatedBuilder(
animation: bgUpdateController,
builder: (context, child) {
return Scaffold(
backgroundColor: background
.chain(
CurveTween(curve: Curves.easeInOutCirc),
)
.evaluate(
AlwaysStoppedAnimation(bgUpdateController.value),
),
...
and the result of my code is:
Conclusion
AnimationController is for how long the animation would be and how to control from time, upper and lower boundary, how to control data with time, length, sequence, etc. while AnimationTween is for the range of animation with time, colour, range, sequence, etc as long the animation would be while.

How to chain multiple controller/animation?

Issue
I made an ellipse loading animation inside of flutter, but had to use a Timer on all three different controllers. (See example below...)
Is there any widget that can help chaining three different animations?
I tried using Interval Widget for multiple curves, but it did not provide a smooth transition. e.g. Interval(0.0, 0.3), Interval(0.3, 0.6), Interval(0.6, 0.9) for the animation curves.
Sample
Sample Code
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:async';
void main() {
runApp(
new MaterialApp(
home: new Scaffold(
body: new CircleLoader(),
)
)
);
}
class CircleLoader extends StatefulWidget {
#override
_CircleLoaderState createState() => new _CircleLoaderState();
}
class _CircleLoaderState extends State<CircleLoader>
with TickerProviderStateMixin {
Animation<double> animation;
Animation<double> animation2;
Animation<double> animation3;
AnimationController controller;
AnimationController controller2;
AnimationController controller3;
int duration = 1000;
Widget circle = new Container(
height: 10.0,
width: 10.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[300],
),
);
#override
initState() {
super.initState();
controller = new AnimationController(
duration: new Duration(milliseconds: duration), vsync: this);
controller2 = new AnimationController(
duration: new Duration(milliseconds: duration), vsync: this);
controller3 = new AnimationController(
duration: new Duration(milliseconds: duration), vsync: this);
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeInOut);
final CurvedAnimation curve2 =
new CurvedAnimation(parent: controller2, curve: Curves.easeInOut);
final CurvedAnimation curve3 =
new CurvedAnimation(parent: controller3, curve: Curves.easeInOut);
animation = new Tween(begin: 0.85, end: 1.5).animate(curve)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
animation2 = new Tween(begin: 0.85, end: 1.5).animate(curve2)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller2.reverse();
} else if (status == AnimationStatus.dismissed) {
controller2.forward();
}
});
animation3 = new Tween(begin: 0.85, end: 1.5).animate(curve3)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller3.reverse();
} else if (status == AnimationStatus.dismissed) {
controller3.forward();
}
});
controller.forward();
new Timer(const Duration(milliseconds: 300), () {
controller2.forward();
});
new Timer(const Duration(milliseconds: 600), () {
controller3.forward();
});
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Container(
width: 100.0,
height: 50.0,
color: Colors.grey,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new ScaleTransition(scale: animation, child: circle),
new ScaleTransition(scale: animation2, child: circle),
new ScaleTransition(scale: animation3, child: circle),
],
),
),
);
}
}
The trick is that you can create your own tween.
In short, what you want is a smooth curve that goes from 0 to 1 and then 1 to 0 smoothly. Which you can assimilate to a (sin(t * 2 * PI) + 1) / 2 where 0 <= t <= 1
And then delay that curve for each cicles.
class TestTween extends Tween<double> {
final double delay;
TestTween({double begin, double end, this.delay}) : super(begin: begin, end: end);
#override
double lerp(double t) {
return super.lerp((sin((t - delay) * 2 * PI) + 1) / 2);
}
}
Which allows to do
_controller = new AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
instead of having to add an animation listener and reverse the animation.
The end result is
class CircleLoader extends StatefulWidget {
#override
_CircleLoaderState createState() => new _CircleLoaderState();
}
class _CircleLoaderState extends State<CircleLoader>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
buildCircle(double delay) {
return new ScaleTransition(
scale: new TestTween(begin: .85, end: 1.5, delay: delay)
.animate(_controller),
child: new Container(
height: 10.0,
width: 10.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[300],
),
),
);
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Container(
width: 100.0,
height: 50.0,
color: Colors.grey,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
buildCircle(.0),
buildCircle(.2),
buildCircle(.4),
],
),
),
);
}
}
class TestTween extends Tween<double> {
final double delay;
TestTween({double begin, double end, this.delay})
: super(begin: begin, end: end);
#override
double lerp(double t) {
return super.lerp((sin((t - delay) * 2 * PI) + 1) / 2);
}
}

Flutter: restart SlideAnimation from current object position

I've got a SlideAnimation on an object that get's triggered every time I tap the screen. The object simply slides up atm but when I tap the screen again the animation starts from the original position of the object again. How would I capture the position the object ended in the last animation and perform the animation from there on?
I've got the following code for the creating of the SlideTransition:
new SlideTransition(
child: new Container(
child: char,
),
position: _characterPosition,
)
And this code for the actual animation:
_characterPosition = new FractionalOffsetTween(
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(0.0, -0.2),
).animate(new CurvedAnimation(
parent: _characterAnimationController,
curve: Curves.easeOut,
)
);
In the simplest case you can use your _characterAnimationController. AnimationController has two related methods stop and forward. Verify current animation's state and invoke required method:
void _onTap() {
if (_characterAnimationController.isAnimating) {
_characterAnimationController.stop(canceled: false);
} else {
_characterAnimationController.forward();
}
}
To extend animation after completion you can reset _characterAnimationController state via value(or create new one) and update position. Complete example:
class Home extends StatefulWidget {
#override
State createState() => new _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
Animation _characterPosition;
AnimationController _characterAnimationController;
FractionalOffset _beginOffset;
FractionalOffset _endOffset;
FractionalOffset _animationOffset;
void _restartAnimation() {
_characterAnimationController.value = 0.0;
_beginOffset = _endOffset;
_endOffset = _endOffset + _animationOffset;
setState(() {
_characterPosition = _generateCharacterPosition();
});
_characterAnimationController.forward();
}
#override
void initState() {
_animationOffset = const FractionalOffset(0.0, 0.15);
_beginOffset = const FractionalOffset(0.0, 0.0);
_endOffset = _animationOffset;
_characterAnimationController = new AnimationController(
duration: new Duration(seconds: 5), vsync: this);
_characterPosition = _generateCharacterPosition();
}
#override
Widget build(BuildContext context) {
return new Material(
child: new InkWell(
child: new SlideTransition(
child: new Container(
child: new Text("Hello"),
),
position: _characterPosition,
),
onTap: _onTap,
),
);
}
void _onTap() {
if (_characterAnimationController.isAnimating) {
_characterAnimationController.stop(canceled: false);
} else if (_characterAnimationController.status ==
AnimationStatus.completed) {
_restartAnimation();
} else {
_characterAnimationController.forward();
}
}
Animation _generateCharacterPosition() => new FractionalOffsetTween(
begin: _beginOffset,
end: _endOffset,
)
.animate(new CurvedAnimation(
parent: _characterAnimationController,
curve: Curves.easeOut,
));
}

Resources