Flutter - Animate a widget to move from GridView to BottomBar upon tapping - flutter

I am looking to animate an image widget to move from a grid view to the bottom bar as shown below but much simpler. Could anyone provide me any guidance as to how to achieve this? I am leaning towards a transform animation, but I have hit a wall trying to calculate the source and destination screen points. Any help is highly appreciated.

Try this package, add_cart_parabola:
import 'dart:ui';
import 'package:add_cart_parabola/add_cart_parabola.dart';
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;
GlobalKey floatKey = GlobalKey();
GlobalKey rootKey = GlobalKey();
Offset floatOffset ;
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
RenderBox renderBox = floatKey.currentContext.findRenderObject();
floatOffset = renderBox.localToGlobal(Offset.zero);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
key: rootKey,
width: double.infinity,
height: double.infinity,
color: Colors.grey,
child: ListView(
children: List.generate(40, (index){
return generateItem(index);
}).toList(),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.yellow,
key: floatKey,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget generateItem(int index){
Text text = Text("item $index",style: TextStyle(fontSize:
25),);
Offset temp;
return GestureDetector(
onPanDown: (details){
temp = new Offset(details.globalPosition.dx, details.globalPosition
.dy);
},
onTap: (){
Function callback ;
setState(() {
OverlayEntry entry = OverlayEntry(
builder: (ctx){
return ParabolaAnimateWidget(rootKey,temp,floatOffset,
Icon(Icons.cancel,color: Colors.greenAccent,),callback,);
}
);
callback = (status){
if(status == AnimationStatus.completed){
entry?.remove();
}
};
Overlay.of(rootKey.currentContext).insert(entry);
});
},
child: Container(
color: Colors.orange,
child: text,
),
);
}
}

Related

CustomPainter's paint method is not getting called before WidgetsBinding.instance.addPostFrameCallback in case of Multiple navigation

I have a Flutter StatefulWidget and in initState() method I am using WidgetsBinding.instance.addPostFrameCallback to use one instance variable (late List _tracks). like -
WidgetsBinding.instance.addPostFrameCallback((_) {
for(itr = 0; itr<_tracks.length; itr++){
// some logic
}
});
As this would get invoked after all Widgets are done. In one of the CustomPaint's painter class I am initializing that variable.
SizedBox.expand(
child: CustomPaint(
painter: TrackPainter(
trackCalculationListener: (tracks) {
_tracks = tracks;
}),
),
),
It is working fine when I have one screen, i.e the same class. But, When I am adding one screen before that and trying to navigate to this screen from the new screen it is throwing _tracks is not initialized exception.
new screen is very basic -
class MainMenu extends StatefulWidget {
const MainMenu({super.key});
#override
State<MainMenu> createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Play(),
maintainState: false));
},
child: const Text('play game'),
),
),
);
}
}
In single screen case the paint method of painter is getting called before postFrameCallback but in case of multiple it is not getting before postFrameCallback and because of that the variable is not getting initialized.
reproducible code -
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/mainMenu': (context) => const MainMenu(),
'/game': (context) => const MyHomePage(title: 'game'),
},
initialRoute: '/mainMenu',
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late List<Rect> _playerTracks;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
print(_playerTracks.length);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
color: Colors.white,
margin: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: SizedBox.expand(
child: CustomPaint(
painter: RectanglePainter(
trackCalculationListener: (playerTracks) =>
_playerTracks = playerTracks),
),
),
),
)
],
),
),
);
}
}
class MainMenu extends StatefulWidget {
static String route = '/mainMenu';
const MainMenu({super.key});
#override
State<MainMenu> createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
height: 200.0,
color: Colors.white,
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/game');
},
child: const Text('play game'),
),
),
),
);
}
}
class RectanglePainter extends CustomPainter {
Function(List<Rect>) trackCalculationListener;
RectanglePainter({required this.trackCalculationListener});
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Offset.zero & size;
const RadialGradient gradient = RadialGradient(
center: Alignment(0.7, -0.6),
radius: 0.2,
colors: <Color>[Color(0xFFFFFF00), Color(0xFF0099FF)],
stops: <double>[0.4, 1.0],
);
canvas.drawRect(
rect,
Paint()..shader = gradient.createShader(rect),
);
List<Rect> _playerTracks = [];
_playerTracks.add(rect);
trackCalculationListener(_playerTracks);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
I am very new to flutter and would highly appreciate if someone could help me figure out what I am doing wrong here.

How can I detect continuously if scrolling is disabled in Flutter?

I want to constantly check if scrolling is not possible.
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((duration) {
print("${_scrollViewController.position.maxScrollExtent}");
// prints true if scrollable else false
print("isScrollable = ${_scrollViewController.position.maxScrollExtent != 0}");
});
}
I've tried this code, but it's only detected once and not continuously.
What should I do?
Use NotificationListener widget.
example:
NotificationListener<ScrollNotification>(
child: ListView(
children: MyListChilren()),
onNotification: (ScrollNotification scrollNotif) {
print(scrollNotif.metrics.maxScrollExtent);
},
);
I implemented what you want by adding 'addPostFrameCallback' to inside of 'build' method like below.
You can check print log by clicking floating button in this example code.
The floating button toggles 'Container' height for changing ListView scrollable or not scrollable.
Whenever called 'build' method, 'addPostFrameCallback' callback is called after rebuild and check whether scroll is scrollable.
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,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
ScrollController _scrollController = ScrollController();
double hhhh = 30;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((duration) {
print("${_scrollController.position.maxScrollExtent}");
// prints true if scrollable else false
print(
"isScrollable = ${_scrollController.position.maxScrollExtent != 0}");
});
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (hhhh == 30) {
hhhh = 3333;
} else {
hhhh = 30;
}
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget _buildBody() {
return ListView(
controller: _scrollController,
children: [
Container(height: hhhh, child: Text('a')),
Container(height: 30, child: Text('a')),
Container(height: 30, child: Text('a')),
Container(height: 30, child: Text('a')),
],
);
}
}

Set StatefulBuilder state outside of StatefulBuilder widget

I'm trying to set a StatefulBuilder widget state outside of its widget. Most examples and the documentation available show the setState method being used inside the widget, however I'm wondering if such method can be called from outside of the StatefulBuilder widget. Here's an example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StackOverflow Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'StackOverflow Example'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new GestureDetector(
//Change one of the icon's color using the tap down function
onTapDown: (TapDownDetails details) {
return changeColor(details);
},
child: Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Center(
child: Column(children: [
//This widget should be rebuilt
StatefulBuilder(
builder: (BuildContext context, StateSetter setState)
{
Color _iconColor = Colors.black;
return Icon(
Icons.money,
size: 50,
color: _iconColor,
);
}
),
//This icon should not be rebuilt
Icon(
Icons.euro,
size: 50,
color: Colors.black,
),
]),
),
),
);
}
void changeColor(TapDownDetails details) {
//Rebuilt StatefulBuilder widget here, but how?
setState(() {
_iconColor = Colors.green;
});
}
}
Currently I get an error because of the _iconColor variable being used in setState. I am also aware that it may be impossible to access it outside of the widget. If that's the case, what would be a better solution to change the icon's color without resorting to rebuilding the whole StatefulWidget?
Thanks for your time.
You can use the ValueListenableBuilder widget.
Example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StackOverflow Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'StackOverflow Example'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ValueNotifier _iconColor = ValueNotifier<Color>(Colors.black);
#override
Widget build(BuildContext context) {
return new GestureDetector(
//Change one of the icon's color using the tap down function
onTapDown: (TapDownDetails details) {
return changeColor(details);
},
child: Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Center(
child: Column(children: [
//This widget should be rebuilt
ValueListenableBuilder(
valueListenable: _iconColor,
builder: (ctx, value, child) {
return Icon(
Icons.money,
size: 50,
color: value,
);
}
),
//This icon should not be rebuilt
Icon(
Icons.euro,
size: 50,
color: Colors.black,
),
]),
),
),
);
}
void changeColor(TapDownDetails details) =>
_iconColor.value = Colors.green
}
This is one way to achieve what you intend, if you have to definitely use the StatefulBuilder.
Basically we are storing the StateSetter that we receive from the StatefulBuilder.builder
class Sample extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return SampleState();
}
}
class SampleState extends State<Sample> {
StateSetter internalSetter;
Color color = Colors.black;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: Text('Sample')),
body: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
color = Colors.deepOrange;
});
},
child: Text('Press'),
),
StatefulBuilder(builder: (context, setter) {
internalSetter = setter;
return Container(
height: 100,
width: 100,
color: color,
);
}),
Undisturbed(),
],
),
);
}
}
class Undisturbed extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Undisturbed is built");
return Container(
width: 100,
height: 100,
color: Colors.red,
);
}
}

Flutter: how to create moveable widget

I have tried to create a moveable text widget.
When I press on widget and start moving finger around screen (still pressing on widget), then position of widget should be also moved.
I have tried to do this with GestureDetector and Transform widgets.
Here is code:
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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MoveText(),
],
),
),
);
}
}
class MoveText extends StatefulWidget{
#override
_MoveTextState createState() => _MoveTextState();
}
class _MoveTextState extends State<MoveText> {
Offset offset = Offset(0.0, 0.0);
#override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
print('${details.localPosition}');
},
onPanStart: (details){
},
onPanUpdate: (details){
print('Pan update ${details.localPosition}');
setState((){
offset = details.localPosition;
});
},
onPanCancel: (){
print('Pan cancel');
},
child: Transform(
transform: Matrix4.translationValues(offset.dx, offset.dy, 0.0),
child: Container(
height: 50,
width: 200,
color: Colors.yellow,
child: Text('Some text for test'),
),
),
);
}
}
When I first tap on widget and start moving everything works great, but when I stop and want again to start moving, then onPanUpdate isn't called.
Does anyone have some solution for this problem?
What you need is a Draggable widget.
Visit for more info: https://api.flutter.dev/flutter/widgets/Draggable-class.html

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.