Flutter rebuild a TweenAnimationBuilder - flutter

I want to make a flipping animation with TweenAnimationBuilder, a container will flip over and change the color. I want to add a button when user click on it, the container will flip over again and change into another colour.
Here is my code:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isBack = true;
Color backColor = Colors.green;
Color topColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
print('building');
return TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: pi),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, _) {
print(value);
if (value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return (Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateY(
pi),
child: Container(
color: topColor,
),
)
),
));
});
}
}
At the first build, the tween value will start from the beginning:
building
0
0
0
0.13080335172486462
0.19619246121668257
0.2180893620122034
...
3.141592653589793
but when I click on the button to change the color, it will not start again from the begin value, it just stays at 3.14:
building
3.141592653589793
Right now the button will only change the color of the container, but it will not flip again.
I suppose after the setstate function, the tween value will restart again at 0, why won't it do so?
Can anybody explain it please?

All you need is an AnimationController and AnimatedBuilder to control the animation.
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
bool isBack = true;
Color backColor = Colors.blue;
Color topColor = Colors.red;
late AnimationController _animationController;
late Animation _rotationAnimation;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
_rotationAnimation =
Tween<double>(begin: 0, end: pi).animate(_animationController);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
// if (_animationController.isDismissed) {
// _animationController.forward();
// } else if (_animationController.isCompleted) {
// _animationController.reverse();
// }
_animationController.forward(from: 0.0);
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
return AnimatedBuilder(
animation: _animationController,
// tween: Tween<double>(begin: 0, end: pi),
// duration: Duration(seconds: 1),
builder: (_, __) {
// print(value);
if (_rotationAnimation.value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(_rotationAnimation.value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..rotateY(pi),
child: Container(
color: topColor,
),
),
),
);
});
}
}

Related

How to navigate to another page if time is over and select automatically a feature from new page?

I have this design:
If time is over, I want to navigate to this page automatically
This is my code:
home_page_timer.dart
import 'package:braintrinig/animation/LongBreak.dart';
import 'package:braintrinig/animation/ShortBreak.dart';
import 'package:braintrinig/animation/StartPomodoro.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HomePageTimerUI extends StatelessWidget {
bool PomoRed = false;
bool ShortYellow = false;
bool LongBlue = false;
#override
Widget build(BuildContext context) {
return Container(
height: 600,
width: double.infinity,
child: DefaultTabController(
length: 3,
child: Scaffold(
bottomNavigationBar: BottomBar(),
appBar: AppBar(
elevation: 1.0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: Size.fromHeight(55),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: Column(
children: <Widget>[
TabBar(
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 4.0),
insets: EdgeInsets.fromLTRB(
12.0, 12.0, 12.0, 11.0)),
indicatorWeight: 15,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Color(0xff3B3B3B),
labelStyle: TextStyle(
fontSize: 12,
letterSpacing: 1.3,
fontWeight: FontWeight.w500),
unselectedLabelColor: Color(0xffD7D7D7),
tabs: [
Tab(
text: "POMODORO",
icon: Icon(Icons.work_history, size: 40),
),
Tab(
text: "SHORT BREAK",
icon: Icon(Icons.ramen_dining, size: 40),
),
Tab(
text: "LONG BREAK",
icon: Icon(Icons.battery_charging_full_rounded,
size: 40),
),
])
],
),
),
),
),
),
body: TabBarView(
children: <Widget>[
Center(
child: StartPomodoro(),
),
Center(
child: ShortBreak(),
),
Center(
child: LongBreak()
),
],
))));
}
}
class BottomBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(20, 20, 20, 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FloatingActionButton.extended(
backgroundColor: Color(0xffF2F2F2),
onPressed: () {},
icon: Icon(Icons.settings,
color: Color(0xff3B3B3B),),
label: Text("Settings",
style: TextStyle(color: Color(0xff3B3B3B),
),)),
FloatingActionButton.extended(
backgroundColor: Color(0xffF2F2F2),
onPressed: () {},
icon: Icon(Icons.show_chart,
color: Color(0xff3B3B3B),),
label: Text("Performance",
style: TextStyle(color: Color(0xff3B3B3B),
),))
],
),
);
}
}
startpomodoro.dart
import 'dart:async';
import 'package:braintrinig/animation/home_page_timer_ui.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:core';
import 'dart:math' as math;
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'ShortBreak.dart';
class StartPomodoro extends StatefulWidget {
const StartPomodoro({Key? key}) : super(key: key);
#override
State<StartPomodoro> createState() => _StartPomodoroState ();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {
FlutterRingtonePlayer.playNotification();
}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: LongBreak? Color(0xffD94530) : Color(0xff6351c5),
body: GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height:300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Color(0xffD94530),
height:
controller.value * MediaQuery.of(context).size.height * 0.742,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child:
Align(
alignment: FractionalOffset.bottomCenter,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
countText,
style: TextStyle(
fontSize: 90.0,
color: Color(0xffF2F2F2),),
),
],
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 15.0),
);
}),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 15.0),
child: FloatingActionButton.extended(
backgroundColor: Color(0xffF2F2F2),
onPressed:() {
if (controller.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller.value == 0 ? 1.0 : controller.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating ?
Icons.pause : Icons.play_arrow,
color: Color(0xff3B3B3B),),
label: Text(
controller.isAnimating ? "PAUSE" : "PLAY",
style: TextStyle(color: Color(0xff3B3B3B)),)),
);
}),
SizedBox(width: 20,),
],
),
],
),
),
],
);
}),
),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
required this.animation,
required this.backgroundColor,
required this.color,
}) : super(repaint: animation);
final Animation<double> animation;
final Color backgroundColor, color;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = backgroundColor
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * math.pi;
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
#override
bool shouldRepaint( CustomTimerPainter old) {
return animation.value != old.animation.value ||
color != old.color ||
backgroundColor != old.backgroundColor;
}
}
shortbreak.dart
import 'dart:async';
import 'package:braintrinig/animation/StartPomodoro.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:core';
import 'dart:math' as math;
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
class ShortBreak extends StatefulWidget {
const ShortBreak({Key? key}) : super(key: key);
#override
State<ShortBreak> createState() => _ShortBreakState ();
}
class _ShortBreakState extends State<ShortBreak>
with TickerProviderStateMixin {
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {
FlutterRingtonePlayer.playNotification();
}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: LongBreak? Color(0xff6351c5) : Color(0xffD94530),
body: GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height:300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Color(0xff6351c5),
height:
controller.value * MediaQuery.of(context).size.height * 0.742,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child:
Align(
alignment: FractionalOffset.bottomCenter,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
countText,
style: TextStyle(
fontSize: 90.0,
color: Color(0xffF2F2F2),),
),
],
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 15.0),
);
}),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 15.0),
child: FloatingActionButton.extended(
backgroundColor: Color(0xffF2F2F2),
onPressed:() {
if (controller.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller.value == 0 ? 1.0 : controller.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating ?
Icons.pause : Icons.play_arrow,
color: Color(0xff3B3B3B),),
label: Text(
controller.isAnimating ? "PAUSE" : "PLAY",
style: TextStyle(color: Color(0xff3B3B3B)),)),
);
}),
SizedBox(width: 20,),
],
),
],
),
),
],
);
}),
),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
required this.animation,
required this.backgroundColor,
required this.color,
}) : super(repaint: animation);
final Animation<double> animation;
final Color backgroundColor, color;
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = backgroundColor
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * math.pi;
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
#override
bool shouldRepaint( CustomTimerPainter old) {
return animation.value != old.animation.value ||
color != old.color ||
backgroundColor != old.backgroundColor;
}
}
How to be able to navigate to shortbreak.dart without losing the design? I mean the appbar on top and bottom.
I tried this:
void notify() {
if (countText == '00:00:00') {
FlutterRingtonePlayer.playNotification();
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=>
StartShortBreak()));
});
}
}
But, unfortunately, the appbars to the top and bottom don't show up.
How to solve this issue? Thank you in advance
you want to navigate between tab view , not between actual Navigator
first, change your homeui to stateful widget with SigleTickerMixin,
instead of using DefaultTabController, make one. :
late TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
and assign that tabcontroller to tabview and tabbar :
TabBar(
controller: _tabController,
TabBarView(
controller: _tabController,
use this
void notify() {
if (countText == '00:00:00') {
FlutterRingtonePlayer.playNotification();
//REMOVE THIS NAVIGATOR
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=>
StartShortBreak()));
});
//Change to this :
_tabController.animateTo(1, duration: const Duration(milliseconds: 300));
}
}
why index is 1, because you put ShortBreak in number 2 on your tabview childreen
In home page. Dart create a tab controller and assign it to the tab bar view. Then create a method to change index
void chageIndex(int index)
{
_tabController.animateTo(index);
}
Then pass this method to promo screen
class StartPomodoro extends StatefulWidget {
final Function (index) changeIndex;
const StartPomodoro({Key? key, required this.changeIndex}) : super(key: key);
#override
State<StartPomodoro> createState() => _StartPomodoroState ();
}
You have to pass it like this
body: TabBarView(
children: <Widget>[
Center(
child: StartPomodoro(changeIndex : changeIndex),
),
Center(
child: ShortBreak(),
),
Center(
child: LongBreak()
),
],
Then in startpromodoro in place of navigator. Push you can use
widget.changeIndex(1);
Inside your condition put this when start the timer or whatever you want to do:
Future.delayed(const Duration(milliseconds: 500), () {
Navigator.pop(context);
});

Finish an animation before starting another - Flutter

I try to add a prestart to a countdown, both have different duration. So i maked two animation A and B,
when i press the start button the animation A start and when it's finished the animation B begin.
What happen to me now, is they starts at the same time, i tried Interval class and Tween class and it didn't works.
My code at this moment looks like this :
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Timer2 extends StatefulWidget {
#override
_Timer2State createState() => _Timer2State();
}
class _Timer2State extends State<Timer2> with TickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(vsync: this,duration: Duration(seconds: 10));
}
#override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: Colors.black,
body: Padding(
padding: EdgeInsets.all(8.0),
child: AnimatedBuilder(
animation: animationController,
builder: (context, child){
return Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.red,
height:
animationController.value * MediaQuery.of(context).size.height,
),
),
Column(
children: <Widget>[
Expanded(
child: Align(
alignment: FractionalOffset.center,
child: AspectRatio(
aspectRatio: 1.0,
child: Stack(
children: <Widget>[
Positioned.fill(
child: AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return CustomPaint(
painter: TimerPainter(
animation: animationController,
backgroundColor: Colors.white,
color: Colors.pink),
);
},
),
),
],
),
),
),
),
Container(
margin: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
child: AnimatedBuilder(
animation: animationController,
builder: (_, Widget child) {
return Icon(
animationController.isAnimating
? Icons.pause
: Icons.play_arrow
);
}),
backgroundColor: Colors.pinkAccent,
onPressed: () {
if (animationController.isAnimating ) {
animationController.stop();
} else {
animationController.reverse(
from: animationController.value == 0.0
? 1.0
: animationController.value);
};
setState(() {
Icon(animationController.isAnimating
? Icons.play_arrow
: Icons.pause
);
});
},
)
],
),
)
],
),
],
);
},
),
),
);
}
}
class TimerPainter extends CustomPainter {
final Animation<double> animation;
final Color backgroundColor;
final Color color;
TimerPainter({this.animation, this.backgroundColor, this.color})
: super(repaint: animation);
#override
void paint(Canvas canvas, Size size) {
paint = Paint()
..color = backgroundColor
..strokeWidth = 5.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
canvas.drawCircle(size.center(Offset.zero), size.width / 2.0, paint);
paint.color = color;
double progress = (1.0 - animation.value) * 2 * 3.14;
canvas.drawArc(Offset.zero & size, 3.14 * 1.5, - progress, false, paint);
}
#override
bool shouldRepaint(TimerPainter old) {
return animation.value != old.animation.value ||
color != old.color ||
backgroundColor != old.backgroundColor;
}
}
Thank you

Size to up animation for widgets

In Flutter you suppose I have a simple Container and I would like to change the size of that to up, for example in this simple screenshot I want to change top container in section 1 to up to have a top container in section 2
and top container in section 1 should behave only 100.0 after size to up
container B in section 1 and section 2 are in the same axis without change position when container A will be resized to up
for example, this is what I want to have
I'm not sure with which one animation I can have this feature
this code work, but this is not what I want to have.
i want to have draggable bottom sheet with changing border radius when bottom sheet arrived to top of screen like with pastes sample video screen and fade0n/out widget inside appbar which that inside top of bottom sheet, which that visible when bottom sheet arrived top or hide when bottom sheet don't have maximum size
import 'package:flutter/material.dart';
void main()=>runApp(SizeUp());
class SizeUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: SizeUpAnim(),
);
}
}
class SizeUpAnim extends StatefulWidget {
#override
State<StatefulWidget> createState() =>_SizeUpAnim();
}
class _SizeUpAnim extends State with SingleTickerProviderStateMixin {
AnimationController _controller;
// ignore: constant_identifier_names
static const _PANEL_HEADER_HEIGHT = 32.0;
bool get _isPanelVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 100), value: 1.0, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 8.0,
title: const Text("Step4"),
leading: IconButton(
onPressed: () {
_controller.fling(velocity: _isPanelVisible ? -1.0 : 1.0);
},
icon: AnimatedIcon(
icon: AnimatedIcons.close_menu,
progress: _controller.view,
),
),
),
body: Column(
children: <Widget>[
Expanded(
child: LayoutBuilder(
builder: _buildStack,
),
),
Text('aaa'),
],
),
);
}
Animation<RelativeRect> _getPanelAnimation(BoxConstraints constraints) {
final double height = constraints.biggest.height;
final double top = height - _PANEL_HEADER_HEIGHT;
const double bottom = -_PANEL_HEADER_HEIGHT;
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0, top, 0.0, bottom),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate( CurvedAnimation(parent: _controller, curve: Curves.linear));
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> animation = _getPanelAnimation(constraints);
final ThemeData theme = Theme.of(context);
return Container(
color: theme.primaryColor,
child: Stack(
children: <Widget>[
const Center(
child: Text("base"),
),
PositionedTransition(
rect: animation,
child: Material(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0)),
elevation: 12.0,
child: Container(
height: _PANEL_HEADER_HEIGHT,
child: const Center(child: Text("panel")),
),
),
),
],
),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('hey'),
RaisedButton(
onPressed: () {
setImages();
setState(() {
isLong = !isLong;
});
},
child: Text(isLong ? 'long' : 'short'),
),
RaisedButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
Navigator.of(context)
.push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images = List.generate(
5,
(_) => 'http://placeimg.com/100/100/any',
);
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key key}) : super(key: key);
final List<String> images;
#override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isStack = false;
bool isBounsing = true;
final double topOffset = 200;
final double miniHandleHeigh = 30;
double safeAreaPadding = 0;
double startBigAnimationOffset;
double startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeigh => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeigh;
startStickyOffset = startBigAnimationOffset + 20;
backgroundHeight = media.size.height - miniHandleHeigh - topOffset;
var canScroll = !_isImageSizeBiggerThenBottomSheetSize(
media.size.width,
media.size.height,
);
controller.addListener(
canScroll ? withoutScrolling : withScrolling,
);
}
void withoutScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else {
controller.animateTo(
0,
duration: Duration(milliseconds: 100),
curve: Curves.easeIn,
);
}
}
void withScrolling() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset && isBig) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset && !isBig) {
setState(() {
isBig = true;
});
} else if (offset > startStickyOffset && !isStack) {
setState(() {
isStack = true;
});
} else if (offset < startStickyOffset && isStack) {
setState(() {
isStack = false;
});
}
if (offset < topOffset && !isBounsing) {
setState(() => isBounsing = true);
} else if (offset > topOffset && isBounsing) {
setState(() => isBounsing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
_isImageSizeBiggerThenBottomSheetSize(
double screenWidth,
double screenHeight,
) {
// padding: 10, 3 in row;
print(screenHeight);
var itemHeight = (screenWidth - 20) / 3;
print(itemHeight);
var gridMaxHeight = screenHeight - topOffset - miniHandleHeigh;
print(gridMaxHeight);
return (widget.images.length / 3).floor() * itemHeight > gridMaxHeight;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: isStack ? Colors.white : Colors.transparent,
body: Stack(
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
constraints: BoxConstraints(minHeight: backgroundHeight),
decoration: BoxDecoration(
color: Colors.white,
),
),
),
ListView(
physics:
isBounsing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 500),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 15),
child: Container(
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(2),
),
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeigh * number + miniHandleHeigh,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 20)),
color: Colors.white,
),
child: Opacity(opacity: 1 - number, child: child),
);
}),
),
Container(
padding: EdgeInsets.all(10),
constraints: BoxConstraints(
minHeight:
MediaQuery.of(context).size.height - savedAppBarHeigh),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
),
if (isStack)
_AppBar(
title: 'Gallery',
)
],
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class _AppBar extends StatelessWidget {
const _AppBar({Key key, this.title}) : super(key: key);
final Color backgroundColor = Colors.white;
final Color color = Colors.grey;
final String title;
#override
Widget build(BuildContext context) {
return Material(
elevation: 5,
child: Container(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
color: backgroundColor,
child: OverflowBox(
alignment: Alignment.topCenter,
maxHeight: 200,
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: kToolbarHeight),
child: AppBar(
centerTitle: false,
backgroundColor: backgroundColor,
iconTheme: IconThemeData(
color: color, //change your color here
),
primary: false,
title: Text(
title,
style: TextStyle(color: color),
),
elevation: 0,
),
),
),
),
),
);
;
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
#required this.builder,
RouteSettings settings,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final result = builder(context);
return Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}
I think you may try this library, sliding_sheet
when you detect the expand status by controller, then you animate/enlarge the container A.

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();
}

flutter notify from top of the screen

I'm trying to figure out how to notify user with alert that comes from top of the screen like normal push notification does.
How can I alert user from top of the screen.
AlertDialog is not customizable so I'm stuck with this. Is there any way to show something like alert or snack bar from top of the screen?
Flutter gives you the possiblity to create notifications with the help of the class Overlay. To animate these entering the screen from the top you can use the SlideTransition in combination with an AnimationController. Here is an example application I created:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
icon: Icon(Icons.notifications_active),
label: Text('Notify!'),
onPressed: () {
Navigator.of(context)
.overlay
.insert(OverlayEntry(builder: (BuildContext context) {
return FunkyNotification();
}));
},
),
),
);
}
}
class FunkyNotification extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyNotificationState();
}
class FunkyNotificationState extends State<FunkyNotification>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> position;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 750));
position = Tween<Offset>(begin: Offset(0.0, -4.0), end: Offset.zero)
.animate(
CurvedAnimation(parent: controller, curve: Curves.bounceInOut));
controller.forward();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 32.0),
child: SlideTransition(
position: position,
child: Container(
decoration: ShapeDecoration(
color: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0))),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text(
'Notification!',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
),
),
),
),
);
}
}
Here you can dismiss notifications using the swipe up or down. This is the perfect notification for promotion in-app.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.fireplace_outlined),
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.transparent,
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return GestureDetector(
onVerticalDragUpdate: (dragUpdateDetails) {
Navigator.of(context).pop();
},
child: Column(
children: [
SizedBox(height: 40),
Card(
margin:
EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Container(
height: 100,
child: Image.asset('lib/model/promo.png',
fit: BoxFit.fill),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
),
],
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: anim1.drive(Tween(
begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0))
.chain(CurveTween(curve: Sprung()))),
child: child,
);
},
);
},
),
);
}
}
class Sprung extends Curve {
factory Sprung([double damping = 20]) => Sprung.custom(damping: damping);
Sprung.custom({
double damping = 20,
double stiffness = 180,
double mass = 1.0,
double velocity = 0.0,
}) : this._sim = SpringSimulation(
SpringDescription(
damping: damping,
mass: mass,
stiffness: stiffness,
),
0.0,
1.0,
velocity,
);
final SpringSimulation _sim;
#override
double transform(double t) => _sim.x(t) + t * (1 - _sim.x(1.0));
}