I have a Stack of 5 Text as children. Each child is wrapped with a FadeTransition object. Outside the Stack I have 5 RaisedButton that maps to one Text each. By default, Text 1 has opacity of 1 and the rest has 0 opacity. When a button is clicked, the opacity of the text that it maps become 1 and the rest become 0.
To do that I created 5 different AnimationController and hardcoded a very long logic. I am not sure that this is the correct way to do that in Flutter.
I believe there must be some easier way.
Moreover, this is a simplified example. The issue in my real application has even complex conditions. (For example, show only Text 2 and Text 3 when Button 5 is clicked and the boolean hasViewedText1 is true .)
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 _animationController1;
AnimationController _animationController2;
AnimationController _animationController3;
AnimationController _animationController4;
AnimationController _animationController5;
#override
void initState() {
super.initState();
_animationController1 = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_animationController2 = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_animationController3 = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_animationController4 = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_animationController5 = new AnimationController(
vsync: this,
duration: new Duration(seconds: 1),
);
_everyThingBackward();
_animationController1.forward();
}
#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(),
// body: _builtLayoutBuilder(),
body: _builtLayoutConditionalAnimation(),
);
}
Widget _builtLayoutConditionalAnimation() {
return new Column(
children: <Widget>[
new Column(
children: <Widget>[
new RaisedButton(child: new Text("Button 1"), onPressed: () {
_everyThingBackward();
_animationController1.forward();
}),
new RaisedButton(child: new Text("Button 2"), onPressed: () {
_everyThingBackward();
_animationController2.forward();
}),
new RaisedButton(child: new Text("Button 3"), onPressed: () {
_everyThingBackward();
_animationController3.forward();
}),
new RaisedButton(child: new Text("Button 4"), onPressed: () {
_everyThingBackward();
_animationController4.forward();
}),
new RaisedButton(child: new Text("Button 5"), onPressed: () {
_everyThingBackward();
_animationController5.forward();
}),
],
),
new Stack(
children: <Widget>[
FadeTransition(opacity: _animationController1,child: new Text('Text 1 is clicked')),
FadeTransition(opacity: _animationController2,child: new Text('Text 2 is clicked')),
FadeTransition(opacity: _animationController3,child: new Text('Text 3 is clicked')),
FadeTransition(opacity: _animationController4,child: new Text('Text 4 is clicked')),
FadeTransition(opacity: _animationController5,child: new Text('Text 5 is clicked')),
],
),
],
);
}
void _everyThingBackward() {
_animationController1.reverse();
_animationController2.reverse();
_animationController3.reverse();
_animationController4.reverse();
_animationController5.reverse();
}
}
This can me made a lot simpler by using the AnimatedSwitcher widget, link to docs.
Here is a full example:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(child: Center(child: Test())),
),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
Widget _child = Text(
"No Button Tapped!",
key: UniqueKey(),
);
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text("Button 1"),
onPressed: () {
setState(() {
_child = Text(
"Button 1 Tapped!",
key: UniqueKey(),
);
});
},
),
RaisedButton(
child: Text("Button 2"),
onPressed: () {
setState(() {
_child = Text(
"Button 2 Tapped!",
key: UniqueKey(),
);
});
},
),
RaisedButton(
child: Text("Button 3"),
onPressed: () {
setState(() {
_child = Text(
"Button 3 Tapped!",
key: UniqueKey(),
);
});
},
),
AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: _child,
),
],
);
}
}
This medium article might be useful too: https://medium.com/flutter-community/what-do-you-know-about-aniamtedswitcher-53cc3a4bebb8
Related
I have an app with a tab bar and to make the UI better, I wanted to make it so that the colour changes when you got to another tab. How do I make it so that when I click or swipe to another tab, let's say the yellow tab, the whole appbar changes to that colour?
Code:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ColorChange>(
create: (context) => ColorChange(),
child: MaterialApp(
theme: ThemeData.light(),
home: SimpleTab(),
),
);
}
}
class _TestPageState extends State<TestPage> {
TabController controller;
class ColorChange extends ChangeNotifier {
Color color = colors[0];
Color getColor() {
return color;
}
void changeColor() {
color = colors[controller.index];
print(color);
notifyListeners();
}
}
List<Color> colors = const [
Colors.green,
Colors.yellow,
Colors.red,
Colors.blue,
Colors.deepOrange,
Colors.deepPurple,
];
class SimpleTab extends StatefulWidget {
#override
_SimpleTabState createState() => _SimpleTabState();
}
class _SimpleTabState extends State<SimpleTab>
with SingleTickerProviderStateMixin {
Tester tester = Tester();
#override
void initState() {
super.initState();
controller = TabController(length: colors.length, vsync: this);
controller.addListener(ColorChange().changeColor);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Simple Tab Demo"),
backgroundColor: Provider.of<ColorChange>(context).getColor(),
bottom: TabBar(
controller: controller,
tabs: [
Tab(
text: 'Green',
),
Tab(
text: 'Yellow',
),
Tab(
text: 'Red',
),
Tab(
text: 'Blue',
),
Tab(
text: 'Orange',
),
Tab(
text: 'Purple',
),
],
isScrollable: true,
),
),
body: TabBarView(
controller: controller,
children: <Widget>[
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
Container(
// child: WidgetThing(tester: tester),
),
],
),
);
}
}
This is just a very simplified demo of my real app. My real app deals with a lot of data fetched from APIs, hence it is probably better if setstate() was not used, because re-building the whole widget may call http requests unnecessarily.
You can try this
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Demo App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SimpleTab(),
);
}
}
class CustomTab {
const CustomTab({this.title, this.color});
final String title;
final Color color;
}
class SimpleTab extends StatefulWidget {
#override
_SimpleTabState createState() => _SimpleTabState();
}
class _SimpleTabState extends State<SimpleTab>
with SingleTickerProviderStateMixin {
TabController controller;
List<CustomTab> tabs = const <CustomTab>[
const CustomTab(title: 'Home', color: Colors.deepOrangeAccent),
const CustomTab(title: 'Setting', color: Colors.blueGrey),
const CustomTab(title: 'Map', color: Colors.teal),
];
CustomTab selectedTab;
#override
void initState() {
super.initState();
controller = new TabController(length: tabs.length, vsync: this);
controller.addListener(_select);
selectedTab = tabs[0];
}
void _select() {
setState(() {
selectedTab = tabs[controller.index];
});
}
#override
Widget build(BuildContext context) {
textStyle() {
return new TextStyle(color: Colors.white, fontSize: 30.0);
}
return new Scaffold(
appBar: new AppBar(
title: new Text("Smiple Tab Demo"),
backgroundColor: selectedTab.color,
bottom: new TabBar(
controller: controller,
tabs: tabs
.map((e) => new Tab(
text: e.title,
))
.toList()),
),
body: new TabBarView(
controller: controller,
children: tabs
.map(
(e) => new Container(
color: e.color,
child: new Center(
child: new Text(
e.title,
style: textStyle(),
),
),
),
)
.toList()),
);
}
}
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 tried to find how to select and upload multiple images in flutter but most of the plugins are not working or I did not understand them well. I found little application but it selects and upload only one picture. How to change this code that user can select and upload multiple pictures or is there any other alternatives. Please write in details, i am freshman in coding. Thanks in advance.
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:toast/toast.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image Upload Demo',
theme: ThemeData(primarySwatch: Colors.pink),
home: ImageInput());
}
}
class ImageInput extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ImageInput();
}
}
class _ImageInput extends State<ImageInput> {
// To store the file provided by the image_picker
File _imageFile;
// To track the file uploading state
bool _isUploading = false;
String baseUrl = 'http://YOUR_IPV4_ADDRESS/flutterdemoapi/api.php';
void _getImage(BuildContext context, ImageSource source) async {
File image = await ImagePicker.pickImage(source: source);
setState(() {
_imageFile = image;
});
// Closes the bottom sheet
Navigator.pop(context);
}
Future<Map<String, dynamic>> _uploadImage(File image) async {
setState(() {
_isUploading = true;
});
// Find the mime type of the selected file by looking at the header bytes of the file
final mimeTypeData =
lookupMimeType(image.path, headerBytes: [0xFF, 0xD8]).split('/');
// Intilize the multipart request
final imageUploadRequest =
http.MultipartRequest('POST', Uri.parse(baseUrl));
// Attach the file in the request
final file = await http.MultipartFile.fromPath('image', image.path,
contentType: MediaType(mimeTypeData[0], mimeTypeData[1]));
// Explicitly pass the extension of the image with request body
// Since image_picker has some bugs due which it mixes up
// image extension with file name like this filenamejpge
// Which creates some problem at the server side to manage
// or verify the file extension
imageUploadRequest.fields['ext'] = mimeTypeData[1];
imageUploadRequest.files.add(file);
try {
final streamedResponse = await imageUploadRequest.send();
final response = await http.Response.fromStream(streamedResponse);
if (response.statusCode != 200) {
return null;
}
final Map<String, dynamic> responseData = json.decode(response.body);
_resetState();
return responseData;
} catch (e) {
print(e);
return null;
}
}
void _startUploading() async {
final Map<String, dynamic> response = await _uploadImage(_imageFile);
print(response);
// Check if any error occured
if (response == null || response.containsKey("error")) {
Toast.show("Image Upload Failed!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
} else {
Toast.show("Image Uploaded Successfully!!!", context,
duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);
}
}
void _resetState() {
setState(() {
_isUploading = false;
_imageFile = null;
});
}
void _openImagePickerModal(BuildContext context) {
final flatButtonColor = Theme.of(context).primaryColor;
print('Image Picker Modal Called');
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 150.0,
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Text(
'Pick an image',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(
height: 10.0,
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Camera'),
onPressed: () {
_getImage(context, ImageSource.camera);
},
),
FlatButton(
textColor: flatButtonColor,
child: Text('Use Gallery'),
onPressed: () {
_getImage(context, ImageSource.gallery);
},
),
],
),
);
});
}
Widget _buildUploadBtn() {
Widget btnWidget = Container();
if (_isUploading) {
// File is being uploaded then show a progress indicator
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: CircularProgressIndicator());
} else if (!_isUploading && _imageFile != null) {
// If image is picked by the user then show a upload btn
btnWidget = Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
child: Text('Upload'),
onPressed: () {
_startUploading();
},
color: Colors.pinkAccent,
textColor: Colors.white,
),
);
}
return btnWidget;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Upload Demo'),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 40.0, left: 10.0, right: 10.0),
child: OutlineButton(
onPressed: () => _openImagePickerModal(context),
borderSide:
BorderSide(color: Theme.of(context).accentColor, width: 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.camera_alt),
SizedBox(
width: 5.0,
),
Text('Add Image'),
],
),
),
),
_imageFile == null
? Text('Please pick an image')
: Image.file(
_imageFile,
fit: BoxFit.cover,
height: 300.0,
alignment: Alignment.topCenter,
width: MediaQuery.of(context).size.width,
),
_buildUploadBtn(),
],
),
);
}
}
I used that package:
dependencies:
multi_image_picker: ^4.6.7
Ex:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:multi_image_picker/multi_image_picker.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Asset> images = List<Asset>();
String _error;
#override
void initState() {
super.initState();
}
Widget buildGridView() {
if (images != null)
return GridView.count(
crossAxisCount: 3,
children: List.generate(images.length, (index) {
Asset asset = images[index];
return AssetThumb(
asset: asset,
width: 300,
height: 300,
);
}),
);
else
return Container(color: Colors.white);
}
Future<void> loadAssets() async {
setState(() {
images = List<Asset>();
});
List<Asset> resultList;
String error;
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 300,
);
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
if (error == null) _error = 'No Error Dectected';
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: Column(
children: <Widget>[
Center(child: Text('Error: $_error')),
RaisedButton(
child: Text("Pick images"),
onPressed: loadAssets,
),
Expanded(
child: buildGridView(),
)
],
),
),
);
}
}
Add dependecy of image_picker:
image_picker: ^0.8.4+3
Then make a method for selectImages():
final ImagePicker imagePicker = ImagePicker();
List<XFile>? imageFileList = [];
void selectImages() async {
final List<XFile>? selectedImages = await
imagePicker.pickMultiImage();
if (selectedImages!.isNotEmpty) {
imageFileList!.addAll(selectedImages);
}
print("Image List Length:" + imageFileList!.length.toString());
setState((){});
}
Create a builder for showing selected Images:
return Scaffold(
appBar: AppBar(
title: Text('Multiple Images'),
),
body: SafeArea(
child: Column(
children: [
ElevatedButton(
onPressed: () {
selectImages();
},
child: Text('Select Images'),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
itemCount: imageFileList!.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(imageFileList![index].path),
fit: BoxFit.cover,);
}),
),
),
],
),
));
Complete Source code available in github link...
https://github.com/NishaJain24/multi_image_picker
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 spent few hours trying to understand why clicking on the IconButton does not toggle change its icon.
import 'package:flutter/material.dart';
import 'dart:core';
class TestIconChange extends StatefulWidget {
#override
_TestIconChangeState createState() => _TestIconChangeState();
}
class _TestIconChangeState extends State<TestIconChange>
with TickerProviderStateMixin {
IconData _iconData = Icons.add;
AnimationController _animationController1;
Widget _child;
#override
void initState() {
super.initState();
_animationController1 = AnimationController(
vsync: this,
value: 1,
duration: Duration(seconds: 1),
);
}
#override
Widget build(BuildContext context) {
if (_child == null) _child = _buildButton();
return Scaffold(
body: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
AnimatedSwitcher(
duration: Duration(milliseconds: 100),
child: _child,
),
RaisedButton(
child: Text('Text Child'),
onPressed: (() {
setState(() {
_child = Text('Dummy text');
});
}),
),
RaisedButton(
child: Text('Button Child'),
onPressed: (() {
setState(() {
_child =_buildButton();
},);
}),
)
],
),
),
),
);
}
Widget _buildButton() {
return IconButton(
onPressed: () {
setState(() {
(_iconData == Icons.add)
? _iconData = Icons.remove
: _iconData = Icons.add;
});
},
icon: Icon(_iconData),
);
}
}
When you call setState it rebuild only Widgets that build in build method.
The main problem was that you didn't do it. You didn't rebuild _child in build method.
This line in your code wrong: if (_child == null) _child = _buildButton();
If you make it like this _child = _buildButton(); then work only button +/-, but not works change to text. Need refactoring of your code!
So, I made refactoring of your code and add ChildType that indicate what type of Widget you want to show: text or button. And then use it in setState method. Now it works, as you expected :)
import 'package:flutter/material.dart';
import 'dart:core';
class TestIconChange extends StatefulWidget {
#override
_TestIconChangeState createState() => _TestIconChangeState();
}
enum ChildType {text, button}
class _TestIconChangeState extends State<TestIconChange>
with TickerProviderStateMixin {
ChildType curChildType = ChildType.button;
IconData _iconData = Icons.add;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
AnimatedSwitcher(
duration: Duration(milliseconds: 100),
child: _buildButton(),
),
RaisedButton(
child: Text('Text Child'),
onPressed: (() {
setState(() {
curChildType = ChildType.text;
});
}),
),
RaisedButton(
child: Text('Button Child'),
onPressed: (() {
setState(() {
curChildType = ChildType.button;
},);
}),
)
],
),
),
);
}
Widget _buildButton() {
if (curChildType == ChildType.text) {
return Text('Dummy text');
}
else {
return IconButton(
icon: Icon(_iconData),
onPressed: () {
setState(() {
_iconData = (_iconData == Icons.add) ? Icons.remove : _iconData = Icons.add;
});
},
);
}
}
}
Good Luck!