How can I expose a StatefulWidget's method? - dart

Say I want to build a StatefulWidget named MySlideWidget that provides a public instance method: animate().
When I press a button on parent of MySlideWidget, then I can call MySlideWidget's animate() method to trigger an internal SlideTransition of MySlideWidget.
The usage would look like this:
class MySlideWidgetDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
MySlideWidget mySlideWidget = new MySlideWidget();
return new Scaffold(
body: mySlideWidget,
floatingActionButton: new FloatingActionButton(
onPressed: () {
mySlideWidget.animate();
},
tooltip: 'Start',
child: new Icon(Icons.add),
));
}
}
What I wondering is how to encapsulate the implementations of AnimationController and _controller.forward() inside MySlideWidget, so user of MySlideWidget can simply call animate().
Is this possible? Or what is the idea way to do encapsulation in Flutter?

You should use AnimationController and pass it to SlideTransition. Then call controller.forward() when needed.
Here is sample:
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<FractionalOffset> _slideTransitionPosition;
#override
initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
_slideTransitionPosition = new FractionalOffsetTween(
begin: const FractionalOffset(0.0, -1.0),
end: const FractionalOffset(0.0, 10.0),
).animate(new CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
));
}
void _onPress() {
_controller.forward();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(
child: new SlideTransition(
position: _slideTransitionPosition,
child: new Container(
color: Colors.red,
width: 100.0,
height: 100.0,
),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _onPress,
tooltip: 'Start',
child: new Icon(Icons.add),
),
);
}
}
Also you can find examples in demos

Related

how to execute the VoidCallback in flutter

I'm trying to test the VoidCallback so I created the main file, that have a function called from a flat button in the widget, which is in a separate file, but did not work.
main.dart
import 'package:flutter/material.dart';
import 'controller_test.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a Custom Form Widget
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
#override
void initState() {
super.initState();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
_test() {
print("hi there");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Con(_test, myController)
);
}
}
controller_test.dart
import 'package:flutter/material.dart';
class Con extends StatelessWidget {
Con(this.clickCallback, this.tc);
final TextEditingController tc;
final VoidCallback clickCallback;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (text) {
print("First text field: $text");
},
),
TextField(
controller: tc,
),
FlatButton(
onPressed: () => clickCallback,
child: Text("click me"),
)
],
),
);
}
}
When I click the FlatButton in the widget, nothing is happening, I was expecting hi there to be printed
there are two options here.
onPressed: () => fun() is like onPressed argument is an anonymous method that calls fun.
onPressed: fun is like onPressed argument is the function fun.
I just found it in another answer here
I was missing the (), so correct call is:
FlatButton(
onPressed: () => clickCallback(),
child: Text("click me"),
)
You can get callback from stateless widget to your current page by using Voidcallback class.
Just add this custom widget in your current page (widget.build()
function)
DefaultButton(
buttonText: Constants.LOGIN_BUTTON_TEXT,
onPressed: () => validateInputFields(),
size: size,
);
My custom widget class is
class DefaultButton extends StatelessWidget {
DefaultButton({this.buttonText, this.onPressed, this.size});
final String buttonText;
final VoidCallback onPressed;
final Size size;
#override
Widget build(BuildContext context) {
return MaterialButton(
minWidth: size.width,
onPressed: () => onPressed(), //callback to refered page
padding: EdgeInsets.all(0.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Ink(
width: size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: <Color>[
SECONDARY_COLOR_SHADE_LITE,
SECONDARY_COLOR_SHADE_DARK,
],
),
borderRadius: BorderRadius.circular(DEFAULT_BORDER_RADIUS),
),
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 12),
child: Text(
buttonText,
style: Theme.of(context).textTheme.button,
textAlign: TextAlign.center,
)),
),
);
}
}
In your callback make sure to call setState if any variables change. I repopulate an list in my provider and then use assign that list to the variable list which I convert into a list of cards. The variable list needs state refreshed to see it.

Shaked animation flutter

I need shaking animation like this video
I'm newbie to Flutter. I would appreciate a solution or a link to the tutorial.
I think there can be better solution. But this one works fine, maybe it'll help
class TestAnimWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TestAnimWidgetState();
}
class _TestAnimWidgetState extends State<TestAnimWidget> with SingleTickerProviderStateMixin {
final TextEditingController textController = TextEditingController();
AnimationController controller;
#override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: 24.0).chain(CurveTween(curve: Curves.elasticIn)).animate(controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedBuilder(
animation: offsetAnimation,
builder: (buildContext, child) {
if (offsetAnimation.value < 0.0) print('${offsetAnimation.value + 8.0}');
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.0),
padding: EdgeInsets.only(left: offsetAnimation.value + 24.0, right: 24.0 - offsetAnimation.value),
child: Center(child: TextField(controller: textController, )),
);
}),
RaisedButton(onPressed: () {
if (textController.value.text.isEmpty) controller.forward(from: 0.0);
},
child: Text('Enter'),)
],
),
);
}
}

OverflowBox,the overflowed part cannot respond to the button?

I need to build a bottomNavigationBar with a vertical overflow TabBar item,
so I tried to use OverflowBox,it looks like useful.
but there is another problew,the overflowed part cannot respond to the button.
so what should I do make the GestureDetector effective?
or you have other ways to build a bottomNavigationBar like this?
thank you very much!
this is screen capture
this is main.dart:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'OverflowBox touch test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
List<Color> _colors = [
Colors.blue,
Colors.green,
Colors.yellow,
];
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
String _tip = "";
TabController controller;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new TabBarView(
controller: controller,
children: <Widget>[
new Center(child: new Text("page 1")),
new Center(child: new Text("page 2")),
new Center(child: new Text("page 3")),
],
),
bottomNavigationBar: new Container(
height: 72.0,
alignment: Alignment.bottomCenter,
color: const Color.fromRGBO(45, 45, 45, 1.0),
child: new TabBar(
controller: controller,
indicatorWeight: 0.01,
tabs: <Widget>[
_getBarItem(0),
_getBarItem(1),
_getBarItem(2),
],
),
),
);
}
#override
void initState() {
super.initState();
controller = new TabController(length: 3, vsync: this);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _getBarItem(int idx){
Widget ret = new Container(
width: 80.0,
height: idx==1?120.0:50.0,
color: _colors[idx],
);
if(idx==1){
ret = new OverflowBox(
maxHeight: double.infinity,
alignment: Alignment.bottomCenter,
child: new Container(
color: Colors.black,
child: new GestureDetector(
onTapDown: (x){
controller.animateTo(idx);
},
child: ret,
),
),
);
}
return ret;
}
}
Try to make your GestureDetector wrap your OverflowBox
Use this behavior on the Gesture Detector and then wrap your overflow with it.
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap(){..
)
You should use a Stack with a Positioned widget instead of an OverflowBox.
PS: Using functions that return widget should be avoided.
I can tell you the reason. OverflowBox widget layout is sizedByParent, so the size is 72. When hit test will ingore the pointer out the size. so the child widget overflowed part cannot respond to the button.

Flutter: Changing the current tab in tab bar view using a button

I am creating an app that contains a tab bar on its homepage. I want to be able to navigate to one of the tabs using my FloatingActionButton. In addition, I want to keep the default methods of navigating to that tab, i.e. by swiping on screen or by clicking the tab.
I also want to know how to link that tab to some other button.
Here is a screenshot of my homepage.
You need to get the TabBar controller and call its animateTo() method from the button onPressed() handle.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyTabbedPage(),
);
}
}
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => new _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
new Tab(text: 'LEFT'),
new Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Tab demo"),
bottom: new TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: new TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
return new Center(child: new Text(tab.text));
}).toList(),
),
floatingActionButton: new FloatingActionButton(
onPressed: () => _tabController.animateTo((_tabController.index + 1) % 2), // Switch tabs
child: new Icon(Icons.swap_horiz),
),
);
}
}
If you use a GlobalKey for the MyTabbedPageState you can get the controller from any place, so you can call the animateTo() from any button.
class MyApp extends StatelessWidget {
static final _myTabbedPageKey = new GlobalKey<_MyTabbedPageState>();
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyTabbedPage(
key: _myTabbedPageKey,
),
);
}
}
You could call it from anywhere doing:
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
I am super late, but hopefully someone benefits from this. just add this line to your onPressed of your button and make sure to change the index number to your preferred index:
DefaultTabController.of(context).animateTo(1);
You can use TabController:
TabController _controller = TabController(
vsync: this,
length: 3,
initialIndex: 0,
);
_controller.animateTo(_currentTabIndex);
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _controller,
tabs: [
...
],
),
),
body: TabBarView(
controller: _controller,
children: [
...
],
),
);
And than, setState to update screen:
int _currentTabIndex = 0;
setState(() {
_currentTabIndex = 1;
});
chemamolin's answer above is correct, but for additional clarification/tip, if you want to call your tabcontroller "from anywhere", also make sure the tabcontroller is not a private property of the class by removing the underscore, otherwise the distant class will not be able to see the tabcontroller with the example provided even when using the GlobalKey.
In other words, change
TabController _tabController;
to:
TabController tabController;
and change
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
to:
MyApp._myTabbedPageKey.currentState.tabController.animateTo(...);
and everywhere else you reference tabcontroller.
If you want to jump to a specific page, you can use
PageController.jumpToPage(int)
However if you need animation, you'd use
PageController.animateToPage(page, duration: duration, curve: curve)
Simple example demonstrating it.
// create a PageController
final _controller = PageController();
bool _shouldAnimate = true; // whether we animate or jump
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_shouldAnimate) {
// animates to page1 with animation
_controller.animateToPage(1, duration: Duration(seconds: 1), curve: Curves.easeOut);
} else {
// jump to page1 without animation
_controller.jumpToPage(1);
}
},
),
body: PageView(
controller: _controller, // assign it to PageView
children: <Widget>[
FlutterLogo(colors: Colors.orange), // page0
FlutterLogo(colors: Colors.green), // page1
FlutterLogo(colors: Colors.red), // page2
],
),
);
}
DefaultTabController(
length: 4,
initialIndex: 0,
child: TabBar(
tabs: [
Tab(
child: Text(
"People",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Events",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Places",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"HashTags",
style: TextStyle(
color: Colors.black,
),
),
),
],
),
)
i was trying to solve similar issue but passing methods or controllers down the widget tree wasn't a clean option for me. i had requirement to go back to tabbed page from other non-tabbed routes (back to specific tabs).
following solution worked for me
Inside tabbed page: read route arguments
#override
Widget build(BuildContext context) {
final String? tabId = Get.arguments;
_selectedTabIndex = tabId !=null? int.parse(tabId): 0;
return Scaffold(
....
body: _pages[_selectedPageIndex]['page'] as Widget,
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
....);
}
Now the calling page
onSubmit:() { // or some other event
// do something here
Get.offAndToNamed(Routes.homeTabs,
arguments: TabIndex.specialTab.index.toString());
//Routes is a const & TabIndex is enum defined somewhere
}
A solution with TabController + Streams
Pass a stream into the state object. Pass the new tab index through the stream for the state to update itself. Here's how I'm doing it.
import 'package:flutter/material.dart';
class TabsWidget extends StatefulWidget {
const TabsWidget({Key? key, this.tabs = const [], this.changeReceiver}) : super(key: key);
final List<Tab> tabs;
// To change the tab from outside, pass in the tab index through a stream
final Stream<int>? changeReceiver;
#override
State<TabsWidget> createState() => _TabsWidgetState();
}
class _TabsWidgetState extends State<TabsWidget> with SingleTickerProviderStateMixin {
int _index = 0;
late TabController _tabController;
#override
void initState() {
_tabController = TabController(length: widget.tabs.length, vsync: this, initialIndex: _index);
// Listen to tab index changes from external sources via this stream
widget.changeReceiver?.listen((int newIndex) {
setState(() {
_index = newIndex;
_tabController.animateTo(newIndex);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
if (widget.tabs.isEmpty) return const SizedBox.shrink(); // If no tabs, show nothing
return TabBar(tabs: widget.tabs, controller: _tabController, );
}
}
// Sample usage - main
import 'dart:async';
import 'package:flutter/material.dart';
import 'tabs_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final StreamController<int> tabChangeNotifier = StreamController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tab Change Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Tab Change Demo'),
),
body: SingleChildScrollView(child: Column(
children: [
const SizedBox(height: 30,),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
ElevatedButton(onPressed: () => tabChangeNotifier.add(0), child: const Text('Go Orange')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(1), child: const Text('Go Red')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(2), child: const Text('Go Green')),
],),
const SizedBox(height: 30,),
TabsWidget(changeReceiver: tabChangeNotifier.stream, tabs: const [
Tab(icon: Icon(Icons.circle, color: Colors.orange,),),
Tab(icon: Icon(Icons.circle, color: Colors.red,),),
Tab(icon: Icon(Icons.circle, color: Colors.green,),),
],),
],
),), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
#override
void dispose() {
tabChangeNotifier.close();
super.dispose();
}
}
This is how the above sample looks.
Use DefaultTabController instead of a local TabController, high enough in your widget tree, and then you'll have access to it from anywhere in that sub tree.
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: initialIndex,
length: tabs.length,
child: SizedBox( // From here down you have access to the tab controller
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SomeWidget(), // Has access to the controller
TabBar(
controller: DefaultTabController.of(context),
tabs:
tabs.map((tab) => Tab(child: Text(tab.title, style: const TextStyle(color: Colors.black)))).toList(),
),
Expanded(
child: TabBarView(
controller: DefaultTabController.of(context),
children: tabs.map((tab) => tab.widget).toList(),
),
),
],
),
),
);
}
In any point in that tree, you can access the tab controller with DefaultTabController.of(context) and change the tab, like so:
DefaultTabController.of(context)?.animateTo(0);
class Tab bar
class TabBarScreen extends StatefulWidget {
TabBarScreen({Key key}) : super(key: key);
#override
_TabBarScreenState createState() => _TabBarScreenState();
}
final List<Tab> tabs = <Tab>[
Tab(text: 'Page1'),
Tab(text: 'Page2'),
];
class _TabBarScreenState extends State<TabBarScreen> with SingleTickerProviderStateMixin {
TabController tabController;
#override
void initState() {
super.initState();
tabController = new TabController(vsync: this, length: tabs.length);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColor,
centerTitle: true,
shape: Border(bottom: BorderSide(color: Colors.white)),
title: Text("Tab Bar",),
bottom: TabBar(
controller: tabController,
tabs: tabs,
indicatorWeight: 5,
indicatorColor: Colors.white,
labelColor: Colors.white,
),
),
body: TabBarView(
controller: tabController,
children: [
PageOneScreen(controller: tabController),
PageTwoScreen(controller: tabController),
],
),
),
);
}
}
class PageOne
class PageOneScreen extends StatefulWidget {
#override
_PageOneScreenState createState() => _PageOneScreenState();
PageOneScreen({controller}) {
tabController = controller;
}
}
TabController tabController;
class _PageOneScreenState extends State<PageOneScreen> {
#override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
onPressed: () {
tabController.animateTo(1); // number : index page
},
child: Text(
"Go To Page 2",
),
),
],
);
}
}

Flutter - Effect of flip card

I'm trying to make a flip card, what would be the best way to get the effect
I would use an AnimatedBuilder or AnimatedWidget to animate the values of a Transform widget. ScaleTransition almost does this for you, but it scales both directions, and you only want one.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePageState createState() => new MyHomePageState();
}
class MyCustomCard extends StatelessWidget {
MyCustomCard({ this.colors });
final MaterialColor colors;
Widget build(BuildContext context) {
return new Container(
alignment: FractionalOffset.center,
height: 144.0,
width: 360.0,
decoration: new BoxDecoration(
color: colors.shade50,
border: new Border.all(color: new Color(0xFF9E9E9E)),
),
child: new FlutterLogo(size: 100.0, colors: colors),
);
}
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _frontScale;
Animation<double> _backScale;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_frontScale = new Tween(
begin: 1.0,
end: 0.0,
).animate(new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 0.5, curve: Curves.easeIn),
));
_backScale = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.5, 1.0, curve: Curves.easeOut),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return new Scaffold(
appBar: new AppBar(),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.flip_to_back),
onPressed: () {
setState(() {
if (_controller.isCompleted || _controller.velocity > 0)
_controller.reverse();
else
_controller.forward();
});
},
),
body: new Center(
child: new Stack(
children: <Widget>[
new AnimatedBuilder(
child: new MyCustomCard(colors: Colors.orange),
animation: _backScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _backScale.value, 1.0);
return new Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
),
new AnimatedBuilder(
child: new MyCustomCard(colors: Colors.blue),
animation: _frontScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _frontScale.value, 1.0);
return new Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
),
],
),
),
);
}
}
I used simple approach, rotated it on X axis. Here is the full code.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
bool _flag = true;
Color _color = Colors.blue;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1), value: 1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.crop_rotate),
onPressed: () async {
if (_flag) {
await _controller.reverse();
setState(() {
_color = Colors.orange;
});
await _controller.forward();
} else {
await _controller.reverse();
setState(() {
_color = Colors.blue;
});
await _controller.forward();
}
_flag = !_flag;
},
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform(
transform: Matrix4.rotationX((1 - _controller.value) * math.pi / 2),
alignment: Alignment.center,
child: Container(
height: 100,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
decoration: BoxDecoration(color: _color.withOpacity(0.2), border: Border.all(color: Colors.grey)),
child: FlutterLogo(colors: _color, size: double.maxFinite),
),
);
},
),
),
);
}
}
You can use the flip_card Flutter package. It lets you define a front and back widget and can be flipped horizontally or vertically.

Resources