I wonder how to animate a container widget to leave the screen to the left.
How do I do that?
Thanks!
This is a way you can do it with SlideTransition
class SlideContainerToTheLeft extends StatefulWidget {
#override
_SlideContainerToTheLeftState createState() =>
_SlideContainerToTheLeftState();
}
class _SlideContainerToTheLeftState extends State<SlideContainerToTheLeft>
with SingleTickerProviderStateMixin {
var tween = Tween<Offset>(begin: Offset.zero, end: Offset(-2, 0))
.chain(CurveTween(curve: Curves.ease));
AnimationController animationController;
#override
void initState() {
// TODO: implement initState
super.initState();
animationController =
AnimationController(vsync: this, duration: Duration(seconds: 2));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SlideTransition(
position: animationController.drive(tween),
child: Container(
width: 300,
height: 400,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), color: Colors.blue),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
animationController.forward();
},
),
);
}
}
Hope this helps you.
Related
I have a widget called Classroom, and in this widget has a button that navigattes you to another page called ButtonPage. It has only one button that triggers a function which affects Classroom's state. It works perfectly. But the problem is that I also want to change LightButton's color with the updated state in Classroom widget. Here is codes.
Classroom
class Classroom extends StatefulWidget {
#override
_ClassroomState createState() => _ClassroomState();
}
class _ClassroomState extends State<Classroom> {
bool isLightOn = false;
final List<String> lightColor = ["red", "green", "blue"];
String currentItem = "red";
void onButtonPress() {
setState(() {
isLightOn = isLightOn;
});
}
void selectItem(String selectedItem) {
setState(() {
currentItem = selectedItem;
});
}
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
padding: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
LightBulb(
isLightOn: isLightOn,
currentItem: currentItem,
),
Container(
child: MaterialButton(
textColor: Colors.white,
color: Colors.blue,
child: Text("Go to button's page"),
onPressed: () {
Navigator.push(context,
MaterialPageRoute<void>(builder: (BuildContext context) {
return ButtonPage(
isLightOn: isLightOn,
onButtonPress: onButtonPress,
);
}));
},
),
),
LightColorSelector(
selectItem: selectItem,
currentItem: currentItem,
lightColor: lightColor,
),
],
),
);
}
}
ButtonPage
class ButtonPage extends StatelessWidget {
final bool isLightOn;
final VoidCallback onButtonPress;
const ButtonPage({this.isLightOn, this.onButtonPress});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Button Page"),
),
body: Center(
child: Container(
child: LightButton(
isLightOn: isLightOn,
onButtonPress: onButtonPress,
),
)),
);
}
}
LightButton
class LightButton extends StatelessWidget {
final bool isLightOn;
final VoidCallback onButtonPress;
const LightButton({this.isLightOn, this.onButtonPress});
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
padding: EdgeInsets.all(5.0),
child: MaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: Colors.blue,
textColor: Colors.white,
child:
isLightOn ?? false ? Text("Turn light off") : Text("Turn light on"),
onPressed: () {
onButtonPress();
},
),
);
}
}
When I click MaterialButton which is labeled as Go to button's page, it navigates me to ButtonPage with isLightOn and onButtonPress variables. In LightButton, when onButtonPress is triggered, it rebuild only previous page. That's why Light Button's label isn't changed. How can I make it affected?
I'm currently using an AnimatedList in my Flutter app and having problems with the way removed list items are animated out. The animation itself works as expected but once the removed item finishes animating, it just disappears causing the other widgets to jump into its place. I had expected the other items to transition into the place of the removed item ...
I tried wrapping my list items with a ScaleTransition but that didn't help - the other list items still do not react to the removed item until it has finished the animation.
This kind of defies the purpose of AnimatedList, right? Or did I do something wrong? The "Widget of the week" video about AnimatedList clearly shows that list items react to newly inserted items by changing their position ...
Here is my code:
#override
Widget build(BuildContext context) {
return AnimatedList(
padding: EdgeInsets.only(top: REGULAR_DIM,
bottom: REGULAR_DIM + kBottomNavigationBarHeight),
initialItemCount: data.length,
itemBuilder: (context, index, animation) {
return MyCustomWidget(
data: data[index],
animation: animation,
disabled: false
);
},
);
}
class MyCustomWidget extends AnimatedWidget {
final MyModel data;
final bool disabled;
MyCustomWidget({
#required this.data,
#required Animation<double> animation,
this.disabled = false
}) : super(listenable: animation);
Animation<double> get animation => listenable;
#override
Widget build(BuildContext context) {
final content = ... ;
return ScaleTransition(
scale: CurvedAnimation(
parent: animation,
curve: Interval(0, 0.25)
).drive(Tween(begin: 0, end: 1)),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: animation.drive(
Tween(begin: Offset(-1, 0), end: Offset(0, 0))
.chain(CurveTween(curve: Curves.easeOutCubic))),
child: content,
),
),
);
}
}
And then somewhere in the MyCustomWidget I invoke this function:
void _remove(BuildContext context) async {
final animatedList = AnimatedList.of(context);
// obtain myModel asynchronously
myModel.removeData(data);
animatedList.removeItem(index, (context, animation) => MyCustomWidget(
data: data,
animation: animation,
disabled: true,
), duration: Duration(milliseconds: 350));
}
The key is to trigger two Transitions one SlideTranstion() and another SizeTransition to eliminate to jump when the item is removed
here is some sample code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text('Update AnimatedList data')),
body: BodyWidget(),
),
);
}
}
class BodyWidget extends StatefulWidget {
#override
BodyWidgetState createState() {
return new BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget>
with SingleTickerProviderStateMixin {
// the GlobalKey is needed to animate the list
final GlobalKey<AnimatedListState> _listKey = GlobalKey(); // backing data
List<String> _data = ['Horse', 'Cow', 'Camel', 'Sheep', 'Goat'];
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 400,
child: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) {
return _buildItem(
_data[index],
animation,
);
},
),
),
RaisedButton(
child: Text(
'Insert single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_onButtonPress();
},
),
RaisedButton(
child: Text(
'Remove single item',
style: TextStyle(fontSize: 20),
),
onPressed: () {
_removeSingleItems();
},
),
],
);
}
Widget _buildItem(String item, Animation<double> animation, {direction: 0}) {
return (direction == 0)
? SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.amber,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
)
: Stack(
children: [
SizeTransition(
sizeFactor: animation,
child: Card(
color: Colors.transparent,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
Align(
alignment: Alignment.topCenter,
heightFactor: 0,
child: SlideTransition(
position: animation
.drive(Tween(begin: Offset(-1, 0), end: Offset(0, 0))),
child: Card(
color: Colors.red,
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
),
),
),
),
],
);
}
void _onButtonPress() {
_insertSingleItem();
}
void _insertSingleItem() {
String item = "Pig";
int insertIndex = 2;
_data.insert(insertIndex, item);
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItems() {
int removeIndex = 2;
String removedItem = _data.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the
// original has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, animation, direction: 1);
};
_listKey.currentState.removeItem(removeIndex, builder);
}
void _updateSingleItem() {
final newValue = 'I like sheep';
final index = 3;
setState(() {
_data[index] = newValue;
});
}
}
enter code here
You need to test the performance with the release version of your app.
I have a button with a text, and when I pressed the button, a text widget with the same text is added to the same page.
I'd like to add Hero like animation between them.
I guess what I need is SlideTransition, but I don't know how to slide from one widget position to another widget position.
Is it possible to do? What widget (or class) should I look into?
Here's the code I want to do (but doesn't work since Hero doesn't work on the same page widgets):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> text = [];
String buttonTag = "0";
#override
Widget build(BuildContext context) {
List<Widget> textWidgets = [];
for (int i = 0; i < text.length; ++i) {
textWidgets.add(
Padding(
padding: const EdgeInsets.all(8.0),
child: Hero(tag: "${i}", child: Text(text[i])),
)
);
}
return SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Hero(
tag: buttonTag,
child: Text("abcde${text.length}")),
onPressed: () {
setState(() {
text.add("abcde${text.length}");
buttonTag = "${text.length}";
});
},
)
] + textWidgets,
),
),
),
);
}
}
Not exactly the answer to the question but instead of Hero (if it's possible) you can use AnimatedList to get the same result.
Code snippet
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<Text> _textWidgets = [];
var rng = new Random();
_addItem() {
setState(() {
_listKey.currentState.insertItem(_textWidgets.length,
duration: const Duration(milliseconds: 500));
int id = rng.nextInt(100);
_textWidgets.add(Text('item $id'));
});
}
Widget _buildItem(
BuildContext context, Text item, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset(0.0, 0.0),
).animate(animation);
return SlideTransition(
position: offsetAnimation,
child: SizedBox(
height: 50.0,
child: Center(
child: item,
),
),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Add Item"),
onPressed: () {
setState(() {
_addItem();
});
},
),
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _textWidgets.length,
itemBuilder: (context, index, animation) {
return _buildItem(context, _textWidgets[index], animation);
},
),
),
],
),
),
);
}
}
I think it's possible by this llibrary which named: LocalHero
Implement your self
The AnimationController, Tween, AnimatedBuilder are key components.
This is a sample and code about this.
Don't use AnimatedController.animate in AnimatedBuilder builder.
evaluate is enough. Because the builder function is called every ticker frame.
Use AnimationController.animate as class member field.
class _AuthorizedState extends State<_Authorized> with SingleTickerProviderStateMixin {
late final _menuAC = AnimationController(vsync: this, duration: 200.ms);
late final isFilterOpen = ValueNotifier(false)..addListener(_handleFilterOpenChanged);
late final filterColor =
ColorTween(begin: context.color.primaryContainer, end: context.color.secondaryContainer)
.animate(_menuAC);
late final filterBorderRadius = Tween<double>(begin: 12, end: 0).animate(_menuAC);
void _handleFilterOpenChanged() {
print(isFilterOpen.value);
if (isFilterOpen.value) {
_menuAC.forward();
} else {
_menuAC.reverse();
}
}
#override
void dispose() {
_menuAC.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AppScaffold(
child: LayoutBuilder(
builder: (context, cons) => Stack(
children: [
AnimatedBuilder(
animation: _menuAC,
builder: (context, child) {
return Positioned(
bottom: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
right: Tween(begin: 16.0, end: 0.0).evaluate(_menuAC),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(12.0),
topRight: const Radius.circular(12.0),
bottomLeft:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
bottomRight:
Radius.circular(Tween<double>(begin: 12, end: 0).evaluate(_menuAC)),
),
child: Container(
width: Tween(begin: 56.0, end: cons.maxWidth).evaluate(_menuAC),
height: Tween(begin: 56.0, end: min(cons.maxHeight * 0.7, 500.0))
.evaluate(_menuAC),
decoration: BoxDecoration(
color: filterColor.value,
),
child: child,
),
),
);
},
child: Material(
elevation: 24,
color: Colors.transparent,
child: InkWell(
onTap: () {
isFilterOpen.value = !isFilterOpen.value;
},
child: const Center(child: Icon(MdiIcons.filter)),
),
),
),
],
),
),
);
}
}
I want to animate my container height after launching the application (after the widget tree is rendered and shown on screen). For example, animate height from 86 to 210.
What I have tried:
class MyAppState extends State<HomePage> with TickerProviderStateMixin {
double appBarHeight = 86.0;
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => animate());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
AnimatedContainer(
curve: Curves.fastOutSlowIn,
child: Container(
color: Colors.red,
),
height: appBarHeight,
duration: Duration(milliseconds: 400),
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(right: 16.0, bottom: 16.0),
child: FloatingActionButton(
child: Icon(Icons.brush),
onPressed: () {
animate();
},
),
))
],
),
);
}
void animate(){
setState(() {
if (appBarHeight == 210.0) appBarHeight = 86.0;
else appBarHeight = 210.0;
});
}
}
But it does not work, because widget is animated before appearing on screen. I see white screen while app is launching and then my widget appears on screen with a final height.
In Android for this purpose we can use addOnLayoutChangeListener().
Is there an analogue of addOnLayoutChangeListener() in Flatter?
As you mentioned, looks like it's an issue on Android, I tested on iOS and the animation runs at the beginning.
You can try this workaround :
_startAnimation(_) async {
await Future.delayed(Duration(milliseconds: 200));
animate();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_startAnimation);
}
I have made one container with scale transition which grows from 0 height and width to 90 height and width.
Now what,I wanted to do is it should slowly fade out as it grows.Do i need to create another animation controller for opacity ? what is the best way to do it ? Can Some one Help?
My Code Look Like This
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyAnimationApp());
class MyAnimationApp extends StatefulWidget {
#override
_MyAnimationAppState createState() => _MyAnimationAppState();
}
class _MyAnimationAppState extends State<MyAnimationApp>
with TickerProviderStateMixin {
Animation<double> animation;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
new AnimationController(vsync: this, duration: Duration(seconds: 3))
..repeat();
animation = new CurvedAnimation(parent: _controller, curve: Curves.linear);
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Container(
child: new Center(
child: new ScaleTransition(
scale: animation,
child: new Container(
decoration: new BoxDecoration(
color: Color(0XFFEC3457), shape: BoxShape.circle),
height: 90.0,
width: 90.0,
),
),
),
),
),
);
}
}
SS is here
Thanks !!! Hoping for a reply ......
You'd need to add a double value to your code:
double _opacity = 1;
And a listener for your animation controller at the end of the initState
_controller.addListener(() {
setState(() {
_opacity = 1 - _controller.value;
});
});
And on your widget tree, you can add this:
ScaleTransition(
scale: animation,
child: Opacity(
opacity: _opacity,
child: Container(