How do I animate a flutter widget on top of a widget that comes after it in the hierarchy? - flutter

I have a simple screen with two widgets inside of a Column widget. When I tap the top widget I want it to move down to the second widget. That part is working, however when it slides down it is going behind the bottom widget instead of in front of the bottom widget. I'm assuming this is because the bottom widget is created second in my code. Is there a way to bring the top widget to the front? Here is my code:
AnimationController _controller;
Animation<Offset> _offsetAnimation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(0.0, 1.0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: Column(
children: <Widget>[
SizedBox(height: 30),
Expanded(
flex: 1,
child: SlideTransition(
key: cardKey,
position: _offsetAnimation,
child: GestureDetector(
onTap: () {
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
}
});
_controller.forward();
},
child: EmptyPile(
title: "First Card",
),
),
),
),
Expanded(flex: 1,
child: EmptyPile(
title: "Second Card",
),
),
]
),
),
);
}
For completeness, but I don't think this matters, here is my EmptyPile code:
class EmptyPile extends StatelessWidget {
EmptyPile({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.7,
margin: EdgeInsets.fromLTRB(0, 30, 0, 30),
decoration: borderDecoration(),
child: Center(
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30.0),
),
),
);
}
}
And finally my border decoration:
BoxDecoration borderDecoration() {
return BoxDecoration(
border: Border.all(
width: 5,
),
borderRadius: BorderRadius.all(
Radius.circular(10)
),
color: Colors.white
);
}

Well as simple as it is I would first of all wrap them in a Stack(). In this case you have access to layers and can define with your sequence which widget is in front and which not.
I'm assuming this is because the bottom widget is created second in my code.
You should have a valid workaround with the above approach.
Hope it helps!

Related

Add custom dropdown on stack flutter

I'm trying to add a custom dropdown menu whose items are just links to other pages
I tried using DropdownButton
But I failed to make its elements as a link and it requires a value, and I do not have a value to pass to it
thank you
You can use OverlayEntry for this case. Below is a simple working example of a dropdown using OverlayEntry:
class TestDropdownWidget extends StatefulWidget {
TestDropdownWidget({Key? key}) : super(key: key);
#override
_TestDropdownWidgetState createState() => _TestDropdownWidgetState();
}
class _TestDropdownWidgetState extends State<TestDropdownWidget>
with TickerProviderStateMixin {
final LayerLink _layerLink = LayerLink();
late OverlayEntry _overlayEntry;
bool _isOpen = false;
//Controller Animation
late AnimationController _animationController;
late Animation<double> _expandAnimation;
#override
void dispose() {
super.dispose();
_animationController.dispose();
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_expandAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
#override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: InkWell(
onTap: _toggleDropdown,
child: Text('Click Me'), //Define your child here
),
);
}
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _toggleDropdown(close: true),
behavior: HitTestBehavior.translucent,
// full screen container to register taps anywhere and close drop down
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Positioned(
left: 100,
top: 100.0,
width: 250,
child: CompositedTransformFollower(
//use offset to control where your dropdown appears
offset: Offset(0, 20),
link: _layerLink,
showWhenUnlinked: false,
child: Material(
elevation: 2,
borderRadius: BorderRadius.circular(6),
borderOnForeground: true,
color: Colors.white,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.grey),
),
child: SizeTransition(
axisAlignment: 1,
sizeFactor: _expandAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//These are the options that appear in the dropdown
Text('Option 1'),
Text('Option 2'),
Text('Option 3'),
Text('Option 4'),
Text('Option 5'),
],
),
),
),
),
),
),
],
),
),
),
);
}
void _toggleDropdown({
bool close = false,
}) async {
if (_isOpen || close) {
_animationController.reverse().then((value) {
_overlayEntry.remove();
if (mounted) {
setState(() {
_isOpen = false;
});
}
});
} else {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)!.insert(_overlayEntry);
setState(() => _isOpen = true);
_animationController.forward();
}
}
}
Here's a gif to show the ui:

In Flutter, what's the easiest, simplest way to make a container flashing once without involving animation widgets?

Imagine Facebook mobile app, where you tap on the notification about someone like your comment. The app will open the appropriate screen, scroll you down to the comment, and after you arrive there, the comment row will flash yellow for a while, rapidly turn transparent, and then it's done.
I just want to make the same flashing animation to a ListView/Column element to let users know that something is happening there as a result of their action. But from what I gathered, to create just a simple animation like that needs a complex elaborate contraption with Animation widgets.
There's a widget that does a much appreciated fade animation called FadeInImage. I just need to provide destination URL, placeholder image asset, and the widget will handle the rest. I'm wondering if there's such alternative where I can just provide a key to a widget, and then call from anywhere: rowKey.currentState.flash(color: Colors.yellow). Or perhaps a way to let me tell the ListView or Column to flash certain row like listViewKey.currentState.items[5].flash(color: Colors.yellow).
There is no a Widget like you are looking for, but you can create a custom widget if you know the Flutter basics. You will be able to build from simple animations to the most advanced ones.
I made a simple example, a list of elements where you can select any element from the list (index).
When you open the screen, you will see the scroll animation, after that, the blink animation will start.
class FlashingHome extends StatelessWidget {
const FlashingHome({Key? key}) : super(key: key);
void _goToWidget(BuildContext context, int index) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => FlashingList(index: index),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 5'),
onPressed: () => _goToWidget(context, 5),
),
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 10'),
onPressed: () => _goToWidget(context, 10),
),
],
),
),
);
}
}
class FlashingList extends StatefulWidget {
const FlashingList({required this.index, Key? key}) : super(key: key);
final int index;
#override
State<FlashingList> createState() => _FlashingListState();
}
class _FlashingListState extends State<FlashingList>
with SingleTickerProviderStateMixin {
final _scrollController = ScrollController();
late final AnimationController _animationController;
final _itemSize = 150.0;
Timer? _timer;
Future<void> _startScrolling() async {
await _scrollController.animateTo(
_itemSize * widget.index,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
// after the scroll animation finishes, start the blinking
_animationController.repeat(reverse: true);
// the duration of the blinking
_timer = Timer(const Duration(seconds: 3), () {
setState(() {
_animationController.stop();
_timer?.cancel();
});
});
}
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
WidgetsBinding.instance!.addPostFrameCallback((_) => _startScrolling());
super.initState();
}
#override
void dispose() {
_timer?.cancel();
_animationController.dispose();
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flashing List'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 15,
itemExtent: 150,
itemBuilder: (context, index) {
final item = Padding(
padding: const EdgeInsets.all(20.0),
child: Text('My Item :$index'),
);
return Padding(
padding: const EdgeInsets.all(4.0),
child: FittedBox(
child: index == widget.index && _animationController.isDismissed
? FadeTransition(
opacity: _animationController,
child: Container(
color: Colors.yellow,
child: item,
),
)
: Container(
color: Colors.grey[200],
child: item,
),
),
);
},
),
);
}
}
Result:
Now that you know how to create an automatic scrolling list, animated item, you can customize this with a more complex animation and extract into a custom widget that you can reuse in your projects.
Reference: https://docs.flutter.dev/development/ui/animations
Try shimmer
While the data is being fetched, you can create a simple animation which will give the feel that something's loading. Here's a simple example.
I am using FAB onPress to reflect the changes.
bool isApiCallProcess = false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
isApiCallProcess = !isApiCallProcess;
});
},
),
backgroundColor: Colors.white,
body: (isApiCallProcess == false)
? CircleAvatar(
backgroundColor:
Colors.black12,
radius: 40,
backgroundImage: AssetImage(
'images/dosa.jpg',
),
):
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Wrap(
children: [
Column(
children: [
const CircleAvatar(
radius: 40,
backgroundImage: AssetImage(
'',
),
),
const SizedBox(
height: 10,
),
Container(
decoration: ShapeDecoration(
color: Colors.grey[400]!,
shape: const
RoundedRectangleBorder(),
),
height: 12,
width: 60,
),
],
),
],
),
),
),
);
Here's the screenshots :

text Translate Animation inside container - flutter

During the text translate animation i want to show only the part of the text that is inside the container. The part of text out side the red container not to be shown.
How can i done translate animation of text widget inside a container.
help to update the code below :
import 'package:flutter/material.dart';
import 'package:flutter_sequence_animation/flutter_sequence_animation.dart';
import 'package:supercharged/supercharged.dart';
class Home2 extends StatefulWidget {
const Home2({Key? key}) : super(key: key);
#override
_Home2State createState() => _Home2State();
}
class _Home2State extends State<Home2> with TickerProviderStateMixin {
late AnimationController animationController;
late SequenceAnimation sequenceAnimation;
#override
void initState() {
// TODO: implement initState
super.initState();
animationController = AnimationController(vsync: this);
sequenceAnimation = SequenceAnimationBuilder()
.addAnimatable(
animatable: Tween<double>(begin: -200, end: 0),
curve: Curves.easeIn,
from: 100.milliseconds,
to: 5000.milliseconds,
tag: "move")
.animate(animationController);
animationController.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Container(
width: 500,
height: 200,
color: Colors.red,
margin: EdgeInsets.all(50),
child: AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: Offset(sequenceAnimation["move"].value,0),
child: Text(
"Welcome",
style: TextStyle(fontSize: 40, color: Colors.black),
),
);
},
),
),
),
);
}
}
Wrap the Transform widget with any Clip Widget.
builder: (BuildContext context, Widget? child) {
return ClipRect(
child: Transform.translate(
offset: Offset(sequenceAnimation["move"].value, 0),
child: Text(
"Welcome",
style: TextStyle(fontSize: 40, color: Colors.black),
),
));
},

Flutter reverse animation doesn't work after animation complete

this is below code is my sample code to implementing simple sliding widget to bottom, animation of translate to bottom work fine, but when i tap to again to close, that doesn't work
and i have another problem as, translating with size of container in this part of code:
Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 0.50))
for example:
Tween<Offset>(begin: Offset.zero, end: Offset(0.0, HEIGHT OF WIDGET ))
full source code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: <Widget>[
TopSlidingLayer(
context,
height: 200.0,
backgroundColor: Colors.indigo,
child: Container(color: Colors.green),
)
],
),
),
);
}
}
class TopSlidingLayer extends StatefulWidget {
final BuildContext context;
final double height;
final Color backgroundColor;
final int animationSpeed;
final Widget child;
TopSlidingLayer(this.context, {this.height = 100.0, this.backgroundColor, this.animationSpeed = 300, #required this.child});
#override
State<TopSlidingLayer> createState() => _TopSlingLayerState();
}
class _TopSlingLayerState extends State<TopSlidingLayer> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _offset;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: widget.animationSpeed));
_offset = Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 0.50)).animate(_controller);
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: Container(
height: widget.height,
decoration: BoxDecoration(
color: Colors.indigo,
),
child: Column(
children: <Widget>[
Expanded(child: widget.child),
InkWell(
onTap: () {
print('tapped');
switch (_controller.status) {
case AnimationStatus.completed:
_controller.reverse();
break;
case AnimationStatus.dismissed:
_controller.forward();
break;
default:
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'click me',
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
);
}
}
The issue is coming from the height in the child container in your SlideTransition widget.
button out the container
When you tap the button, it will move out of the container so you will not be able to click on it again.
So I removed the height to have a full screen container and instead, I put a sizebox around the inkwell to give the same result as you have.
class _TopSlingLayerState extends State<TopSlidingLayer>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _offset;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: widget.animationSpeed));
_offset = Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 0.20))
.animate(_controller);
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: Container(
child: Column(
children: <Widget>[
Container(child: widget.child, height: widget.height),
InkWell(
onTap: () {
print('tapped ${_controller.status}');
switch (_controller.status) {
case AnimationStatus.completed:
_controller.reverse();
break;
case AnimationStatus.dismissed:
_controller.forward();
break;
default:
}
},
child: SizedBox(
width: double.infinity,
child: Container(
decoration: BoxDecoration(
color: Colors.indigo,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'click me',
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
),
),
),
],
),
),
);
}
}
I don't know if it answers well your issue.

Flutter - Flip animation - Flip a card over its right or left side based on the tap's location

I've started playing with Flutter and now thinking about the best way how I can implement a card's flipping animation.
I found this flip_card package and I'm trying to adjust its source code to my needs.
Here is the app which I have now:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(FlipAnimationApp());
class FlipAnimationApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flip animation"),
),
body: Center(
child: Container(
width: 200,
height: 200,
child: WidgetFlipper(
frontWidget: Container(
color: Colors.green[200],
child: Center(
child: Text(
"FRONT side.",
),
),
),
backWidget: Container(
color: Colors.yellow[200],
child: Center(
child: Text(
"BACK side.",
),
),
),
),
),
),
),
);
}
}
class WidgetFlipper extends StatefulWidget {
WidgetFlipper({
Key key,
this.frontWidget,
this.backWidget,
}) : super(key: key);
final Widget frontWidget;
final Widget backWidget;
#override
_WidgetFlipperState createState() => _WidgetFlipperState();
}
class _WidgetFlipperState extends State<WidgetFlipper> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> _frontRotation;
Animation<double> _backRotation;
bool isFrontVisible = true;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_frontRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: pi / 2).chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(pi / 2),
weight: 50.0,
),
],
).animate(controller);
_backRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(pi / 2),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween(begin: -pi / 2, end: 0.0).chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
],
).animate(controller);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
AnimatedCard(
animation: _backRotation,
child: widget.backWidget,
),
AnimatedCard(
animation: _frontRotation,
child: widget.frontWidget,
),
_tapDetectionControls(),
],
);
}
Widget _tapDetectionControls() {
return Stack(
fit: StackFit.expand,
children: <Widget>[
GestureDetector(
onTap: _leftRotation,
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 1.0,
alignment: Alignment.topLeft,
child: Container(
color: Colors.transparent,
),
),
),
GestureDetector(
onTap: _rightRotation,
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 1.0,
alignment: Alignment.topRight,
child: Container(
color: Colors.transparent,
),
),
),
],
);
}
void _leftRotation() {
_toggleSide();
}
void _rightRotation() {
_toggleSide();
}
void _toggleSide() {
if (isFrontVisible) {
controller.forward();
isFrontVisible = false;
} else {
controller.reverse();
isFrontVisible = true;
}
}
}
class AnimatedCard extends StatelessWidget {
AnimatedCard({
this.child,
this.animation,
});
final Widget child;
final Animation<double> animation;
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
var transform = Matrix4.identity();
transform.setEntry(3, 2, 0.001);
transform.rotateY(animation.value);
return Transform(
transform: transform,
alignment: Alignment.center,
child: child,
);
},
child: child,
);
}
}
Here is how it looks like:
What I'd like to achieve is to make the card flip over its right side if it was tapped on its right half and over its left side if it was tapped on its left half. If it is tapped several times in a row on the same half it should flip over the same side (not back and forth as it is doing now).
So the desired animation should behave as the following one from Quizlet app.
You should know when you tap on the right or left to change the animations dynamically, for that you could use a flag isRightTap. Then, you should invert the values of the Tweens if it has to rotate to one side or to the other.
And the side you should rotate would be:
Rotate to left if the front is visible and you tapped on the left, or, because the back animation is reversed, if the back is is visible and you tapped on the right
Otherwise, rotate to right
Here are the things I changed in _WidgetFlipperState from the code in the question:
_updateRotations(bool isRightTap) {
setState(() {
bool rotateToLeft = (isFrontVisible && !isRightTap) || !isFrontVisible && isRightTap;
_frontRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: rotateToLeft ? (pi / 2) : (-pi / 2))
.chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (-pi / 2) : (pi / 2)),
weight: 50.0,
),
],
).animate(controller);
_backRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (pi / 2) : (-pi / 2)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween(begin: rotateToLeft ? (-pi / 2) : (pi / 2), end: 0.0)
.chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
],
).animate(controller);
});
}
#override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_updateRotations(true);
}
void _leftRotation() {
_toggleSide(false);
}
void _rightRotation() {
_toggleSide(true);
}
void _toggleSide(bool isRightTap) {
_updateRotations(isRightTap);
if (isFrontVisible) {
controller.forward();
isFrontVisible = false;
} else {
controller.reverse();
isFrontVisible = true;
}
}
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage();
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _toggler = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(actions: [
TextButton(
onPressed: _onFlipCardPressed,
child: const Text('change', style: TextStyle(color: Colors.white)),
)
]),
body: Center(
child: SizedBox.square(
dimension: 140,
child: FlipCard(
toggler: _toggler,
frontCard: AppCard(title: 'Front'),
backCard: AppCard(title: 'Back'),
),
),
),
);
}
void _onFlipCardPressed() {
setState(() {
_toggler = !_toggler;
});
}
}
class AppCard extends StatelessWidget {
final String title;
const AppCard({
required this.title,
});
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.deepPurple[400],
),
child: Center(
child: Text(
title,
style: const TextStyle(
fontSize: 40.0,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
);
}
}
class FlipCard extends StatelessWidget {
final bool toggler;
final Widget frontCard;
final Widget backCard;
const FlipCard({
required this.toggler,
required this.backCard,
required this.frontCard,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 800),
transitionBuilder: _transitionBuilder,
layoutBuilder: (widget, list) => Stack(children: [widget!, ...list]),
switchInCurve: Curves.ease,
switchOutCurve: Curves.ease.flipped,
child: toggler
? SizedBox(key: const ValueKey('front'), child: frontCard)
: SizedBox(key: const ValueKey('back'), child: backCard),
),
);
}
Widget _transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnimation = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnimation,
child: widget,
builder: (context, widget) {
final isFront = ValueKey(toggler) == widget!.key;
final rotationY = isFront ? rotateAnimation.value : min(rotateAnimation.value, pi * 0.5);
return Transform(
transform: Matrix4.rotationY(rotationY)..setEntry(3, 0, 0),
alignment: Alignment.center,
child: widget,
);
},
);
}
}
Try this code I've made some changes to your code, now the GestureDetector is divided equally in width on widget so when you tap on the left side of the box it will reverse the animation and if you tap on right side part it will forward the animation.
Widget _tapDetectionControls() {
return Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: GestureDetector(
onTap: _leftRotation,
),
),
Expanded(
flex: 1,
child: GestureDetector(
onTap: _rightRotation,
),
),
],
);
}
void _leftRotation() {
controller.reverse();
}
void _rightRotation() {
controller.forward();
}