Is there way to do the hover menu bar in flutter web?
Already tried to used OverlayEntry and mouse region to detect whether it is hover, but it seems that there is a gap time between move from menu bar to menu item.
Example code for the item:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:overlay_container/overlay_container.dart';
class DropDownMenu extends StatefulWidget {
final String title;
final List<String> route;
final List<String> subTitle;
final BorderRadius borderRadius;
final RenderBox renderBox;
const DropDownMenu({
Key key,
this.title,
this.borderRadius,
this.renderBox,
this.route,
this.subTitle,
}) : super(key: key);
@override
DropDownMenuState createState() => DropDownMenuState();
}
class DropDownMenuState extends State<DropDownMenu> with SingleTickerProviderStateMixin {
GlobalKey _key;
bool isMenuOpen = false;
Offset buttonPosition;
Size buttonSize;
OverlayEntry _overlayEntry;
BorderRadius _borderRadius;
AnimationController _animationController;
RenderBox renderBox;
bool isHover = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 100),
);
_borderRadius = widget.borderRadius ?? BorderRadius.circular(4);
_key = LabeledGlobalKey("button_icon");
renderBox = widget.renderBox;
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void closeMenu() {
_overlayEntry.remove();
_animationController.reverse();
isMenuOpen = !isMenuOpen;
}
void openMenu() {
findButton();
_animationController.forward();
_overlayEntry = _overlayEntryBuilder();
Overlay.of(context).insert(_overlayEntry);
isMenuOpen = !isMenuOpen;
}
findButton() {
RenderBox renderBox = _key.currentContext.findRenderObject();
buttonSize = renderBox.size;
buttonPosition = renderBox.localToGlobal(Offset.zero);
}
@override
Widget build(BuildContext context) {
return Container(
height: 200,
key: _key,
padding: EdgeInsets.only(
left: 20,
),
color: Colors.amberAccent,
child: InkWell(
// onTap: () {
// print('---');
// if (isMenuOpen) {
// closeMenu();
// } else {
// openMenu();
// }
// },
// onHover: (isHover) {
// print(isHover);
// this.isHover = isHover;
// if (isMenuOpen) {
// closeMenu();
// } else {
// openMenu();
// }
// },
child: Column(
children: [
Container(
height: 60,
child: Text(
widget.title ?? '',
style: TextStyle(
color: isHover ? Colors.yellow : Colors.white,
),
),
),
// OverlayContainer(
// show: true,
// // Let's position this overlay to the right of the button.
// position: OverlayContainerPosition(
// // Left position.
// buttonPosition.dx,
// // Bottom position.
// buttonPosition.dy + buttonSize.height - 15,
// ),
// // The content inside the overlay.
// child: Container(
// height: 70,
// padding: const EdgeInsets.all(20),
// margin: const EdgeInsets.only(top: 5),
// decoration: BoxDecoration(
// color: Colors.white,
// boxShadow: <BoxShadow>[
// BoxShadow(
// color: Colors.grey[300],
// blurRadius: 3,
// spreadRadius: 6,
// )
// ],
// ),
// child: Text("I render outside the \nwidget hierarchy."),
// ),
// ),
],
),
),
);
}
OverlayEntry _overlayEntryBuilder() {
return OverlayEntry(
builder: (context) {
return Positioned(
top: buttonPosition.dy + buttonSize.height - 15,
left: buttonPosition.dx,
width: buttonSize.width,
child: Material(
color: Colors.transparent,
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.topCenter,
child: ClipPath(
clipper: ArrowClipper(),
child: Container(
width: 17,
height: 17,
color: Color(0xFFF),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Container(
height: buttonSize.height * 5 * 10,
decoration: BoxDecoration(
borderRadius: _borderRadius,
),
child: Theme(
data: ThemeData(
iconTheme: IconThemeData(),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(widget.subTitle.length, (index) {
return GestureDetector(
onTap: () {
Get.toNamed(widget.route[index] ?? '/');
},
child: Container(
width: buttonSize.width,
height: buttonSize.height + 10,
padding: EdgeInsets.only(bottom: 10),
color: Colors.amberAccent,
child: Center(
child: SelectableText(
widget.subTitle[index] ?? '',
style: TextStyle(
color: Colors.yellow,
),
),
),
),
);
}),
),
),
),
),
],
),
),
);
},
);
}
}
class ArrowClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, size.height);
path.lineTo(size.width / 2, size.height / 2);
path.lineTo(size.width, size.height);
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
There is another package example: https://pub.dev/packages/overlay_container