Stack position not accurate - flutter

I want to add red blinking dot on the container when it is tapped, but the dot position is not accurate.
How to fix?
MyApp
import 'package:flutter/material.dart';
import 'blinking_dot.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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
double posx;
double posy;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: Stack(
children: <Widget>[
Container(
height: double.infinity,width: double.infinity,
padding: EdgeInsets.all(10),
child: Image.asset("assets/img.jpg")),
Positioned(
child: BlinkingDot(),
left: posx,
top: posy,
)
],
)));
}
}
blinking_dot.dart
import 'package:flutter/material.dart';
class BlinkingDot extends StatefulWidget {
#override
_BlinkingDotState createState() => _BlinkingDotState();
}
class _BlinkingDotState extends State<BlinkingDot>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 1));
_animationController.repeat();
super.initState();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animationController,
child: Container(
height: 15,
width: 15,
child: FloatingActionButton(
backgroundColor: Colors.redAccent,
)));
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
Output

posy = localOffset.dy- MediaQuery.of(context).padding.top - kToolbarHeight;
also you need to decrease offset by half of the red dot size
in your case if will something like this
posx = localOffset.dx - 7.5;
posy = localOffset.dy- MediaQuery.of(context).padding.top - kToolbarHeight - 7.5;

Are you looking like this?
Home page
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
double posx;
double posy;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy-70.0;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("widget.title"),
),
body: GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: Stack(
children: <Widget>[
Container(
height: double.infinity,
width: double.infinity,
padding: EdgeInsets.all(10),
child: Image.asset("assets/img.jpg")),
Positioned(
child: BlinkingDot(),
left: posx,
top: posy,
)
],
),
));
}
}
BlinkingDot page
class BlinkingDot extends StatefulWidget {
#override
_BlinkingDotState createState() => _BlinkingDotState();
}
class _BlinkingDotState extends State<BlinkingDot>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 1));
_animationController.repeat();
super.initState();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animationController,
child: Container(
height: 15,
width: 15,
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.redAccent,
)));
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}

It's because you did not consider the following
You need to subtract AppBar height from dy.
You need to subtract the circle radius from both dx and dy.
You need to subtract the top padding from dy and left padding from
dx.
Do the following to get the expected result
posx = localOffset.dx - MediaQuery.of(context).padding.left - circleRadius;
posy = localOffset.dy -MediaQuery.of(context).padding.top - circleRadius - kToolbarHeight;
Here is the complete snippet
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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
//int _counter = 0;
double posx;
double posy;
final circleRadius = 7.5;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx =
localOffset.dx - MediaQuery.of(context).padding.left - circleRadius;
posy = localOffset.dy -MediaQuery.of(context).padding.top - circleRadius - kToolbarHeight;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: Stack(
children: <Widget>[
Container(
height: double.infinity,
width: double.infinity,
padding: EdgeInsets.all(10),
child: Image.asset("assets/img.jpg")),
Positioned(
child: BlinkingDot(circleRadius: circleRadius),
left: posx,
top: posy,
)
],
)));
}
}
class BlinkingDot extends StatefulWidget {
final double circleRadius;
const BlinkingDot({Key key, this.circleRadius}) : super(key: key);
#override
_BlinkingDotState createState() => _BlinkingDotState();
}
class _BlinkingDotState extends State<BlinkingDot>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
_animationController =
new AnimationController(vsync: this, duration: Duration(seconds: 1));
_animationController.repeat();
super.initState();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animationController,
child: Container(
height: widget.circleRadius * 2,
width: widget.circleRadius * 2,
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.redAccent,
)));
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
See the live demo here.

Related

Flutter - Custom Animation Using CustomPaint

I created a CustomPaint widget and I want to change the heigth from zero to end of screen with smooth animation as shown in the image.
You can use Flutter's AnimationController to drive explicit CustomPaint animation as follows (DartPad):
class AnimatedHeightCustomPaint extends StatefulWidget {
final AnimationController controller;
final Size size;
final Color color;
final Curve curve;
const AnimatedHeightCustomPaint({Key? key,
required this.controller,
required this.size,
required this.color,
this.curve = Curves.linear}) : super(key: key);
#override
State<StatefulWidget> createState() => AnimatedHeightCustomPaintState();
}
class AnimatedHeightCustomPaintState extends State<AnimatedHeightCustomPaint> {
#override
void initState() {
super.initState();
// Listen to the animation progress and update the custom paint (height in this case)
widget.controller.addListener(() => setState(() { }));
}
#override
Widget build(BuildContext context) => CustomPaint(
painter: AnimatedHeightPainter(
// Here we can apply some fancy animation progress like bounce in
heightProps: widget.curve.transform(widget.controller.value),
color: widget.color,
),
// Since you want to change the height, you need to provide a size
size: widget.size,
);
}
class AnimatedHeightPainter extends CustomPainter
{
// Progress of the animation, i.e. between 0.0 and 1.0
final double heightProps;
final Color color;
AnimatedHeightPainter({required this.heightProps, required this.color});
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Offset(0.0, size.height * (1 - heightProps)) & Size(size.width, size.height * heightProps),
Paint()..color = color,
);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
As a complete sample,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
// Flutter's AnimationController
late AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, // the SingleTickerProviderStateMixin
duration: const Duration(seconds: 2),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: AnimatedHeightCustomPaint(
controller: _animationController,
size: MediaQuery.of(context).size,
color: Colors.red,
curve: Curves.bounceInOut,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// In Parent widget, you can control the animation (back and forth here)
if(_animationController.isCompleted) {
_animationController.reverse();
}else if(_animationController.isDismissed) {
_animationController.forward();
}
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class AnimatedHeightCustomPaint extends StatefulWidget {
final AnimationController controller;
final Size size;
final Color color;
final Curve curve;
const AnimatedHeightCustomPaint({Key? key,
required this.controller,
required this.size,
required this.color,
this.curve = Curves.linear}) : super(key: key);
#override
State<StatefulWidget> createState() => AnimatedHeightCustomPaintState();
}
class AnimatedHeightCustomPaintState extends State<AnimatedHeightCustomPaint> {
#override
void initState() {
super.initState();
// Listen to the animation progress and update the custom paint (height in this case)
widget.controller.addListener(() => setState(() { }));
}
#override
Widget build(BuildContext context) => CustomPaint(
painter: AnimatedHeightPainter(
// Here we can apply some fancy animation progress like bounce in
heightProps: widget.curve.transform(widget.controller.value),
color: widget.color,
),
// Since you want to change the height, you need to provide a size
size: widget.size,
);
}
class AnimatedHeightPainter extends CustomPainter
{
// Progress of the animation, i.e. between 0.0 and 1.0
final double heightProps;
final Color color;
AnimatedHeightPainter({required this.heightProps, required this.color});
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Offset(0.0, size.height * (1 - heightProps)) & Size(size.width, size.height * heightProps),
Paint()..color = color,
);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
Perhaps You can even use AnimatedContainer if there is no specific reason to use CustomPaint widget (DartPad):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
// Flutter's AnimationController
bool isExpanded = true;
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
curve: Curves.bounceInOut,
width: screenSize.width,
height: screenSize.height * (isExpanded ? 1: 0),
duration: const Duration(seconds: 2),
color: Colors.red,
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() => isExpanded = !isExpanded);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Simple BounceIn Image Flutter Animation

What i want?
I want to do simple animation with this image.
What does it mean?
If i click on it, it should make smooth for example bounce in animation. Im open in ways through which we can achieve this effect.
What i tried?
I thought AnimatedContainer will get done but curve: parameter is not doing anything.
ps. I am beginner
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Poppy App',
theme: ThemeData(
primarySwatch: Colors.brown,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
double _imgwidth = 200;
double _imgheight = 200;
class _MyHomePageState extends State<MyHomePage> {
void poopAnimationIn() async {
setState(() {
_imgheight = 300;
_imgwidth = 300;
});
await Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
_imgheight = 200;
_imgwidth = 200;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: const Color.fromARGB(164, 117, 81, 1),
child: Center(
child: GestureDetector(
onTap: () {
poopAnimationIn();
},
child: AnimatedContainer(
curve: Curves.bounceIn,
duration: const Duration(milliseconds: 200),
child: Image.network(
'https://picsum.photos/250?image=9',
width: _imgwidth,
height: _imgheight,
)),
),
),
));
}
}
You need to specify the size for AnimatedContainer and change that size in setState.
Here is the edited code:
I suggest you use other curves like easeIn instead of bounceIn to have a smoother animation
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Poppy App',
theme: ThemeData(
primarySwatch: Colors.brown,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double initialHeight = 200;
double initialImageHeight = initialHeight;
double expandedImageHeight = 300;
void poopAnimationIn() async {
setState(() {
initialImageHeight = expandedImageHeight;
});
await Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
initialImageHeight = initialHeight;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: const Color.fromARGB(164, 117, 81, 1),
child: Center(
child: GestureDetector(
onTap: () async {
poopAnimationIn();
},
child: AnimatedContainer(
height: initialImageHeight,
curve: Curves.bounceIn,
duration: const Duration(milliseconds: 200),
child: Image.network(
'https://picsum.photos/250?image=9',
// width: _imgwidth,
// height: _imgheight,
))),
),
),
// ),
);
}
}

Flutter Reworked question: Issue sharing states between widget with button and widget with countdown timer

I am trying since some days to connect an animated stateful child widget with a countdown timer to the parent stateful widget with the user interaction. I found this answer from Andrey on a similar question (using Tween which I do not) that already helped a lot, but I still don't get it to work. My assumption is, the child's initState could be the reason. The timer's code comes from here.
I have removed quite some code including some referenced functions/classes. This should provide a clearer picture on the logic:
In MainPageState I declare and init the _controller of the animation
In MainPageState I call the stateless widget CreateKeypad hosting among others the "go" key
When go is clicked, this event is returned to MainPageState and _controller.reverse(from: 1.0); executed
In MainPageState I call the stateful widget CountDownTimer to render the timer
In _CountDownTimerState I am not sure if my initState is correct
In _CountDownTimerState I build the animation with CustomTimerPainter from the timer code source
The animation shall render a white donut and a red, diminishing arc on top. However, I only see the white donut, not the red timer's arc. Any hint is highly appreciated.
class MainPage extends StatefulWidget {
MainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
AnimationController _controller;
var answer="0", correctAnswer = true, result = 0;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 7));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
),
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
CreateKeypad( // creates a keypad with a go button. when go is clicked, countdown shall start
prevInput: int.parse((answer != null ? answer : "0")),
updtedInput: (int val) {
setState(() => answer = val.toString());
},
goSelected: () {
setState(() {
if (answer == result.toString()) {
correctAnswer = true;
}
final problem = createProblem();
result = problem.result;
});
_controller.reverse(from: 1.0); // start the countdown animation
Future.delayed(const Duration(milliseconds: 300,),
() => setState(() => correctAnswer = true));
},
),
CountDownTimer(_controller), // show countdown timer
]
),
),
)
);
}
}
// CREATE KEYPAD - all keys but "1! and "go" removed
class CreateKeypad extends StatelessWidget {
final int prevInput;
final VoidCallback goSelected;
final Function(int) updtedInput;
CreateKeypad({#required this.prevInput, #required this.updtedInput, this.goSelected});
#override
Widget build(BuildContext context) {
return Row(
children: <Widget> [
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0, height: 80.0,
child: CupertinoButton(
child: Text("1", style: TextStyle(color: CupertinoColors.black)),
onPressed: () {
updtedInput(1);
},
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0, height: 80.0,
child: CupertinoButton(
child: Text("Go!", style: TextStyle(color: CupertinoColors.black)),
onPressed: () => goSelected(),
),
),
),
],
),
]
);
}
}
// CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1
class CountDownTimer extends StatefulWidget {
CountDownTimer(this._controller);
final AnimationController _controller;
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer> with TickerProviderStateMixin {
#override
void initState() {
super.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
String get timerString {
Duration duration = widget._controller.duration * widget._controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60)
.toString()
.padLeft(2, '0')}';
}
#override
Widget build(BuildContext context) {
return Container(
child: AnimatedBuilder(
animation: widget._controller,
builder:
(BuildContext context, Widget child) {
return CustomPaint(
painter: CustomTimerPainter( // this draws a white donut and a red diminishing arc on top
animation: widget._controller,
backgroundColor: Colors.white,
color: Colors.red,
));
},
),
);
}
}
You can copy paste run full code below
Step 1: You can put controller inside CountDownTimerState
Step 2: Use GlobalKey
CountDownTimer(key: _key)
Step 3: Call function start() inside _CountDownTimerState with _key.currentState
goSelected: () {
setState(() {
...
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
...
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
...
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MainPage extends StatefulWidget {
MainPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> with TickerProviderStateMixin {
AnimationController _controller;
var answer = "0", correctAnswer = true, result = 0;
GlobalKey _key = GlobalKey();
#override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
//navigationBar: CupertinoNavigationBar(),
child: SafeArea(
child: Container(
color: Colors.blue,
child: Column(children: <Widget>[
CreateKeypad(
// creates a keypad with a go button. when go is clicked, countdown shall start
prevInput: int.parse((answer != null ? answer : "0")),
updtedInput: (int val) {
setState(() => answer = val.toString());
},
goSelected: () {
setState(() {
if (answer == result.toString()) {
correctAnswer = true;
}
/*final problem = createProblem();
result = problem.result;*/
});
print("go");
_controller.reverse(from: 10.0); // start the countdown animation
final _CountDownTimerState _state = _key.currentState;
_state.start();
/* Future.delayed(
const Duration(
milliseconds: 300,
),
() => setState(() => correctAnswer = true));*/
},
),
Container(
height: 400,
width: 400,
child: CountDownTimer(key: _key)), // show countdown timer
]),
),
));
}
}
// CREATE KEYPAD - all keys but "1! and "go" removed
class CreateKeypad extends StatelessWidget {
final int prevInput;
final VoidCallback goSelected;
final Function(int) updtedInput;
CreateKeypad(
{#required this.prevInput, #required this.updtedInput, this.goSelected});
#override
Widget build(BuildContext context) {
return Row(children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("1", style: TextStyle(color: CupertinoColors.black)),
onPressed: () {
updtedInput(1);
},
),
),
),
Padding(
padding: const EdgeInsets.all(2.0),
child: SizedBox(
width: 80.0,
height: 80.0,
child: CupertinoButton(
child:
Text("Go!", style: TextStyle(color: CupertinoColors.black)),
onPressed: () => goSelected(),
),
),
),
],
),
]);
}
}
// CREATE COUNTDOWN https://medium.com/flutterdevs/creating-a-countdown-timer-using-animation-in-flutter-2d56d4f3f5f1
class CountDownTimer extends StatefulWidget {
CountDownTimer({Key key}) : super(key: key);
//final AnimationController _controller;
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 7));
super
.initState(); // here I have some difference to Andrey's answer because I do not use Tween
}
String get timerString {
Duration duration = _controller.duration * _controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
void start() {
setState(() {
_controller.reverse(from: 1.0);
});
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return CustomPaint(
painter: CustomTimerPainter(
// this draws a white donut and a red diminishing arc on top
animation: _controller,
backgroundColor: Colors.green,
color: Colors.red,
));
},
),
);
}
}
class CustomTimerPainter extends CustomPainter {
CustomTimerPainter({
this.animation,
this.backgroundColor,
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;
//print("progress ${progress}");
canvas.drawArc(Offset.zero & size, math.pi * 1.5, -progress, false, paint);
}
#override
bool shouldRepaint(CustomTimerPainter old) {
//print(animation.value);
return true;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MainPage(),
);
}
}

Flutter manage dx of Offset by swiping to right or left

in Flutter application i want to divide width of screen to 10 part and when user swipe to right or left i could detect each part of this swipe, for example
after divide screen to 10 part i have a variable named screenParts as double which that has 0.0 by default, when user swipe to right the variable value should be plus 1 part and swipe to left should be minus the variable value minus
this variable value should be between 0.0 and 1, you could consider Tween<double>
double screenParts = 0.0;
final double screenWidth = MediaQuery.of(context).size.width / 10;
i want to use this value inside into this part of code:
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0) {
if (screenParts / screenWidth == 0) screenParts = screenParts += 0.1;
} else if (details.delta.dx < 0) {
if (screenParts / screenWidth == 0) screenParts = screenParts -= 0.1;
}
setState(() {});
},
child: SafeArea(
child: FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.fastLinearToSlowEaseIn),
child: SlideTransition(
position: Tween<Offset>(
begin: animateDirection,
end: Offset(screenParts, 0), //<---- this part
).animate(CurvedAnimation(
parent: animation,
curve: Curves.fastLinearToSlowEaseIn,
)),
// ignore: void_checks
child: Material(elevation: 8.0, child: child)),
),
),
);
it means i try to manage dx of Offset by screenParts value with swiping to right or left
is this what you are looking for?
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: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
double position = 0.0;
#override
void initState() {
super.initState();
}
void handleXChange(double deltaX) {
setState(() {
position = deltaX / MediaQuery.of(context).size.width;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Text(position.toString()),
DragArea(
handleXChange: handleXChange,
),
],
);
}
}
class DragArea extends StatefulWidget {
const DragArea({Key key, #required this.handleXChange}) : super(key: key);
final void Function(double newX) handleXChange;
#override
_DragAreaState createState() => _DragAreaState();
}
class _DragAreaState extends State<DragArea> {
double initX;
void onPanStart(DragStartDetails details) {
initX = details.globalPosition.dx;
}
void onPanUpdate(DragUpdateDetails details) {
var x = details.globalPosition.dx;
var deltaX = x - initX;
widget.handleXChange(deltaX);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: onPanStart,
onPanUpdate: onPanUpdate,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
),
);
}
}

Flutter add custom SlideTransition to ModalRoute

in this below implemented code i can show dialog on bottom of page with Fade animation and now, i want to add SlideTransition to ModalRoute of this implementation to slide dialog from bottom, but i can't to do that
for example, what i want to have:
source code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Open the popup window',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showPopup(context, _popupBody(), 'Popup Demo');
},
tooltip: 'Open Popup',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
showPopup(BuildContext context, Widget widget, String title, {BuildContext popupContext}) {
Navigator.push(
context,
PopupLayout(
top: MediaQuery.of(context).size.height * 0.300,
left: 0,
right: 0,
bottom: 0,
child: PopupContent(
content: Scaffold(
body: widget,
),
),
),
);
}
Widget _popupBody() {
return Container(
child: Text('This is a popup window'),
);
}
}
class PopupLayout extends ModalRoute {
double top;
double bottom;
double left;
double right;
Color bgColor;
final Widget child;
#override
Duration get transitionDuration => Duration(milliseconds: 200);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => bgColor == null ? Colors.black.withOpacity(0.5) : bgColor;
#override
String get barrierLabel => null;
#override
bool get maintainState => false;
PopupLayout({Key key, this.bgColor, #required this.child, this.top, this.bottom, this.left, this.right});
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
if (top == null) this.top = 10;
if (bottom == null) this.bottom = 20;
if (left == null) this.left = 20;
if (right == null) this.right = 20;
return GestureDetector(
onTap: () {
// call this method here to hide soft keyboard
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
child: Material(
// This makes sure that text and other content follows the material style
type: MaterialType.transparency,
//type: MaterialType.canvas,
// make sure that the overlay content is not cut off
child: SafeArea(
bottom: true,
child: _buildOverlayContent(context),
),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: this.bottom, left: this.left, right: this.right, top: this.top),
child: SlideTransition(child: child),
);
}
#override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return FadeTransition(opacity: animation, child: child);
}
}
class PopupContent extends StatefulWidget {
final Widget content;
PopupContent({
Key key,
this.content,
}) : super(key: key);
_PopupContentState createState() => _PopupContentState();
}
class _PopupContentState extends State<PopupContent> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: widget.content,
);
}
}
Here is a working example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with TickerProviderStateMixin {
void showPopup() {
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 400), vsync: this);
showDialog(
context: context,
builder: (_) => PopUp(
controller: controller,
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
final AnimationController controller;
PopUp({this.controller});
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> {
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 600, end: 200);
Animation<double> marginTopAnimation;
AnimationStatus animationStatus;
#override
void initState() {
super.initState();
marginTopAnimation = marginTopTween.animate(widget.controller)
..addListener(() {
animationStatus = widget.controller.status;
if (animationStatus == AnimationStatus.dismissed) {
Navigator.of(context).pop();
}
if(this.mounted) {
setState(() {
});
}
});
widget.controller.forward();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(widget.controller),
child: GestureDetector(
onTap: () {
widget.controller.reverse();
},
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
),
color: Colors.red,
child: Text("Container"),
),
),
),
);
}
#override
void dispose() {
widget.controller.dispose();
super.dispose();
}
}
UPDATE 1: Added Material as a child of Container to fix the barrier not dismissing bug.
UPDATE 2: Made a few more changes which reverses the animation when the barrier is dismissed.
NOTE: The screenshot does not reflect the updated changes. It is a demo of the original answer.