Flutter hero animation between widgets not screens - flutter

Hero animation is the best for navigating between screen, but I need same animation between widgets. Like one card moving another place for example: Product Card moves to shoppingcart and something else. Thanks for answers!

Try this one, add_to_cart_animation:
import 'package:add_to_cart_animation/add_to_cart_animation.dart';
import 'package:add_to_cart_animation/add_to_cart_icon.dart';
import 'package:flutter/material.dart';
import 'list_item.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: 'Add To Cart Animation',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Add To Cart Animation'),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// We can detech the location of the card by this GlobalKey<CartIconKey>
GlobalKey<CartIconKey> gkCart = GlobalKey<CartIconKey>();
late Function(GlobalKey) runAddToCardAnimation;
var _cartQuantityItems = 0;
#override
Widget build(BuildContext context) {
return AddToCartAnimation(
// To send the library the location of the Cart icon
gkCart: gkCart,
rotation: true,
dragToCardCurve: Curves.easeIn,
dragToCardDuration: const Duration(milliseconds: 1000),
previewCurve: Curves.linearToEaseOut,
previewDuration: const Duration(milliseconds: 500),
previewHeight: 30,
previewWidth: 30,
opacity: 0.85,
initiaJump: false,
receiveCreateAddToCardAnimationMethod: (addToCardAnimationMethod) {
// You can run the animation by addToCardAnimationMethod, just pass trough the the global key of the image as parameter
this.runAddToCardAnimation = addToCardAnimationMethod;
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: false,
actions: [
// Improvement/Suggestion 4.4 -> Adding 'clear-cart-button'
IconButton(
icon: Icon(Icons.cleaning_services),
onPressed: () {
_cartQuantityItems = 0;
gkCart.currentState!.runClearCartAnimation();
},
),
SizedBox(width: 16),
AddToCartIcon(
key: gkCart,
icon: Icon(Icons.shopping_cart),
colorBadge: Colors.red,
),
SizedBox(
width: 16,
)
],
),
body: ListView(
children: [
AppListItem(onClick: listClick, index: 1),
AppListItem(onClick: listClick, index: 2),
AppListItem(onClick: listClick, index: 3),
AppListItem(onClick: listClick, index: 4),
AppListItem(onClick: listClick, index: 5),
AppListItem(onClick: listClick, index: 6),
AppListItem(onClick: listClick, index: 7),
],
),
),
);
}
// Improvement/Suggestion 4.4 -> Running AddTOCartAnimation BEFORE runCArtAnimation
void listClick(GlobalKey gkImageContainer) async {
await runAddToCardAnimation(gkImageContainer);
await gkCart.currentState!.runCartAnimation((++_cartQuantityItems).toString());
}
}
OR
[not null safety]
this is a sample of add to cart, 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() {
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,
),
);
}
}

For animating widget in the same screen you can use AnimatedPositioned widget see the below code
import 'dart:math';
import 'package:flutter/material.dart';
class AnimatedPositionedDemo extends StatefulWidget {
const AnimatedPositionedDemo({Key? key}) : super(key: key);
static String routeName = 'animated_positioned';
#override
_AnimatedPositionedDemoState createState() => _AnimatedPositionedDemoState();
}
class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
late double topPosition;
late double leftPosition;
double generateTopPosition(double top) => Random().nextDouble() * top;
double generateLeftPosition(double left) => Random().nextDouble() * left;
#override
void initState() {
super.initState();
topPosition = generateTopPosition(30);
leftPosition = generateLeftPosition(30);
}
void changePosition(double top, double left) {
setState(() {
topPosition = generateTopPosition(top);
leftPosition = generateLeftPosition(left);
});
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final appBar = AppBar(title: const Text('AnimatedPositioned'));
final topPadding = MediaQuery.of(context).padding.top;
// AnimatedPositioned animates changes to a widget's position within a Stack
return Scaffold(
appBar: appBar,
body: SizedBox(
height: size.height,
width: size.width,
child: Stack(
children: [
AnimatedPositioned(
top: topPosition,
left: leftPosition,
duration: const Duration(seconds: 1),
child: InkWell(
onTap: () => changePosition(
size.height -
(appBar.preferredSize.height + topPadding + 50),
size.width - 150),
child: Container(
alignment: Alignment.center,
width: 150,
height: 50,
child: Text(
'Click Me',
style: TextStyle(
color:
Theme.of(context).buttonTheme.colorScheme!.onPrimary,
),
),
color: Theme.of(context).primaryColor,
),
),
),
],
),
),
);
}
}
I hope it works for you

For Animated widgets, flutter team has provided a video on youtube here
And you can read all about them on their website here

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 make a moveable overlay?

I want to show a minimize moveable calling screen in top of the app
I tried with stack it does not meet my expectation
#Raiyan, you have to use picture-in-picture concept to implement such floating child.
In flutter, multiple plugins are there, that we can use for the, some are as follows:
https://pub.dev/packages/pip_view
https://pub.dev/packages/floating
https://pub.dev/packages/easy_pip
floating package will fit in your case, it provides picture in Picture mode management for Flutter.
Sadly the gif is not working... But by on taping and draging on the green window will make the green window move.
Try this:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return OverlayWindow(
overlayChild: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Overlay Window",
style: TextStyle(fontSize: 20),
),
Icon(
Icons.android,
size: 80,
),
],
),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
class OverlayWindow extends StatefulWidget {
const OverlayWindow(
{Key? key, required this.overlayChild, required this.child})
: super(key: key);
final Widget overlayChild;
final Widget child;
#override
State<OverlayWindow> createState() => _OverlayWindowState();
}
class _OverlayWindowState extends State<OverlayWindow> {
double _top = 0;
double _left = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
Positioned(
top: _top,
left: _left,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_top = max(0, _top + details.delta.dy);
_left = max(0, _left + details.delta.dx);
});
},
child: Container(
height: 300,
width: 200,
color: Colors.green,
child: widget.overlayChild,
),
),
)
],
);
}
}
More about things like that, you can find here:
https://docs.flutter.dev/development/ui/advanced/gestures

How can I scroll down and focus to a specific widget in flutter?

I am implementing a tutorial of app using https://pub.dev/packages/tutorial_coach_mark . This marked button of beyond the view. So when I need to target this button, I need to scroll/focus this specific part. But I can not find any solution. Can anyone help me with that please?
One Idea is to , Make one Listview with all your widgets . then
Use this :
scroll_to_index: ^2.1.1
Example:
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.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: 'Scroll To Index Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Scroll To Index Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const maxCount = 100;
static const double maxHeight = 1000;
final random = math.Random();
final scrollDirection = Axis.vertical;
late AutoScrollController controller;
late List<List<int>> randomList;
#override
void initState() {
super.initState();
controller = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection);
randomList = List.generate(maxCount,
(index) => <int>[index, (maxHeight * random.nextDouble()).toInt()]);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
IconButton(
onPressed: () {
setState(() => counter = 0);
_scrollToCounter();
},
icon: Text('First'),
),
IconButton(
onPressed: () {
setState(() => counter = maxCount - 1);
_scrollToCounter();
},
icon: Text('Last'),
)
],
),
body: ListView(
scrollDirection: scrollDirection,
controller: controller,
children: randomList.map<Widget>((data) {
return Padding(
padding: EdgeInsets.all(8),
child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),
);
}).toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: _nextCounter,
tooltip: 'Increment',
child: Text(counter.toString()),
),
);
}
int counter = -1;
Future _nextCounter() {
setState(() => counter = (counter + 1) % maxCount);
return _scrollToCounter();
}
Future _scrollToCounter() async {
await controller.scrollToIndex(counter,
preferPosition: AutoScrollPosition.begin);
controller.highlight(counter);
}
Widget _getRow(int index, double height) {
return _wrapScrollTag(
index: index,
child: Container(
padding: EdgeInsets.all(8),
alignment: Alignment.topCenter,
height: height,
decoration: BoxDecoration(
border: Border.all(color: Colors.lightBlue, width: 4),
borderRadius: BorderRadius.circular(12)),
child: Text('index: $index, height: $height'),
));
}
Widget _wrapScrollTag({required int index, required Widget child}) =>
AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: child,
highlightColor: Colors.black.withOpacity(0.1),
);
}
This will work Perfectly
final dataKey = new GlobalKey();
SingleChildScrollView(
child: Column(
children: [
otherwidgets(),
otherwidgets(),
Container(
key: controller.dataKey,
child: helpPart(context),
),
otherwidgets(),
otherwidgets(),
],
),
on action: Scrollable.ensureVisible(dataKey.currentContext!);
This worked for me!

Child widget send dynamic data

I have a two-page app. On-Page One I am showing an UUID which changes every 1 second. It is shown using listview. Once the user clicks on the list view it goes to the second page and shows the data on that card.
It should have been the changing UUID. but the data shown is static UUID. How I can pass the data changed on page 1 to page 2?
import 'dart:async';
import 'package:uuid/uuid.dart';
import 'package:uuid/uuid_util.dart';
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'),
);
}
}
List<EuropeanCountries> europeanCountries = [];
class EuropeanCountries {
String myText;
String myUuid;
EuropeanCountries({
this.myText,
this.myUuid,
});
}
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;
int _perPage = 50;
ScrollController _myScrollController = ScrollController();
void _incrementCounter() async {
const ThreeSec = const Duration(seconds: 1);
this._counter++;
europeanCountries.insert(
0,
EuropeanCountries(
myText: this._counter.toString(),
));
print(europeanCountries[0].myText);
setState(() {});
}
void getMoreData() {
print('adding More Product ');
europeanCountries.add(EuropeanCountries(
myText: this._counter.toString(),
));
//europeanCountries.insert(0, EuropeanCountries(myText:this._counter.toString(), myButtonText: "", myColor: Colors.blue));
setState(() {});
}
void generateUUID() async {
var uuid = Uuid();
for (int i = 0; i < 6000; i++) {
await new Future.delayed(new Duration(milliseconds: 1000));
for (EuropeanCountries currCountry in europeanCountries) {
currCountry.myUuid = uuid.v1();
}
setState(() {});
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
generateUUID();
_myScrollController.addListener(() {
double maxscroll = _myScrollController.position.maxScrollExtent;
double currentScroll = _myScrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
print("mac Scroll Controller - " + maxscroll.toString());
print("Current Scroll Controller - " + currentScroll.toString());
print("delta Scroll Controller - " + delta.toString());
if ((maxscroll - currentScroll) < delta) {
getMoreData();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _myListView(context),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _myListView(BuildContext context) {
// backing data
return Container(
child: europeanCountries.length == 0
? Center(
child: Text('No Product to Display'),
)
: ListView.builder(
controller: _myScrollController,
itemCount: europeanCountries.length,
reverse: false,
itemBuilder: (context, index) {
return myContainer(index: index);
},
),
);
}
}
class myContainer extends StatefulWidget {
final int index;
const myContainer({Key key, this.index}) : super(key: key);
#override
_myContainerState createState() => _myContainerState();
}
class _myContainerState extends State<myContainer> {
#override
Widget build(BuildContext context) {
return Container(
height: 120,
decoration: BoxDecoration(
border: Border.all(color: Colors.blue[700]),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
margin: EdgeInsets.all(20),
child: Column(
children: <Widget>[
Text(europeanCountries[widget.index].myText),
SizedBox(
height: 15,
),
RaisedButton(
child: Text('Detail'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondRoute(
myCountry: europeanCountries[widget.index],
)),
);
},
color: Colors.blue[700],
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
splashColor: Colors.black,
),
Text(europeanCountries[widget.index].myUuid != null
? europeanCountries[widget.index].myUuid
: 'Default')
],
),
);
}
}
class SecondRoute extends StatefulWidget {
final EuropeanCountries myCountry;
const SecondRoute({Key key, this.myCountry}) : super(key: key);
#override
_SecondRouteState createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
#override
void didUpdateWidget(SecondRoute oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text(widget.myCountry.myUuid != null
? widget.myCountry.myText
: 'default'),
),
SizedBox(height: 15),
Center(
child: Text(widget.myCountry.myUuid != null
? widget.myCountry.myUuid
: 'default'),
),
],
),
),
),
);
}
}
https://imgur.com/VqRfcZY
<blockquote class="imgur-embed-pub" lang="en" data-id="VqRfcZY"></blockquote><script async src="//s.imgur.com/min/embed.js" charset="utf-8"></script>

Flutter PopupMenuButton onLongPressed

I'm trying to show a menu context on a custom widget I created when it is long pressed(on tap has another behaviour).
I tried to use GestureDetector with onLongPress and use the function showMenu but it shows the menu in the corner, not over the widget pressed. I've seen a workaround to get the position of the widget and pass it to the showMenu but it looks messy to me.
return new GestureDetector(
child: _defaultBuild(),
onTap: onTap,
onLongPress: () {
showMenu(
items: <PopupMenuEntry>[
PopupMenuItem(
//value: this._index,
child: Row(
children: <Widget>[
Text("Context item1")
],
),
)
],
context: context,
position: _getPosition(context)
);
}
);
RelativeRect _getPosition(BuildContext context) {
final RenderBox bar = context.findRenderObject();
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
return position;
}
I've tried also to use PopupMenuButton but I wasn't able to show the menu onLongPressed.
Any ideas?
showMenu() works well on my end. It looks like the issue has something to do with how the menu is being positioned with RelativeRect. Instead of RelativeRect.fromRect(), I used RelativeRect.fromSize() on mine.
RelativeRect _getRelativeRect(GlobalKey key){
return RelativeRect.fromSize(
_getWidgetGlobalRect(key), const Size(200, 200));
}
Rect _getWidgetGlobalRect(GlobalKey key) {
final RenderBox renderBox =
key.currentContext!.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
debugPrint('Widget position: ${offset.dx} ${offset.dy}');
return Rect.fromLTWH(offset.dx / 3.1, offset.dy * 1.05,
renderBox.size.width, renderBox.size.height);
}
Here's a complete sample that you can try.
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(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final widgetKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
key: widgetKey,
child: Container(
height: 60,
width: 120,
color: Colors.lightBlueAccent,
child: const Center(child: Text('Show Menu')),
),
onLongPress: () {
showMenu(
items: <PopupMenuEntry>[
PopupMenuItem(
//value: this._index,
child: Row(
children: const [Text("Context item 1")],
),
)
],
context: context,
position: _getRelativeRect(widgetKey),
);
},
),
),
);
}
RelativeRect _getRelativeRect(GlobalKey key){
return RelativeRect.fromSize(
_getWidgetGlobalRect(key), const Size(200, 200));
}
Rect _getWidgetGlobalRect(GlobalKey key) {
final RenderBox renderBox =
key.currentContext!.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
debugPrint('Widget position: ${offset.dx} ${offset.dy}');
return Rect.fromLTWH(offset.dx / 3.1, offset.dy * 1.05,
renderBox.size.width, renderBox.size.height);
}
}