Skip to content

Instantly share code, notes, and snippets.

@hectorAguero
Last active December 17, 2024 14:27
Show Gist options
  • Save hectorAguero/9907539303159f822907bace7147a7f4 to your computer and use it in GitHub Desktop.
Save hectorAguero/9907539303159f822907bace7147a7f4 to your computer and use it in GitHub Desktop.
Flutter CompositedTransform with Listenable
import 'package:flutter/material.dart';
// Based on https://medium.com/snapp-x/creating-custom-dropdowns-with-overlayportal-in-flutter-4f09b217cfce
// And https://github.com/flutter/flutter/issues/138828
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SizedBox(
width: 300,
child: CustomDropDown(),
),
),
),
);
}
}
class ButtonWidget extends StatelessWidget {
const ButtonWidget({
super.key,
this.height = 48,
this.width,
this.onTap,
this.child,
required this.isExpanded,
});
final double? height;
final double? width;
final VoidCallback? onTap;
final Widget? child;
final bool isExpanded;
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: Container(
decoration: BoxDecoration(
borderRadius: isExpanded
? BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
)
: BorderRadius.circular(10),
color: isExpanded ? Colors.red : Colors.grey,
border: isExpanded
? Border(
top: BorderSide(color: Colors.black),
right: BorderSide(color: Colors.black),
left: BorderSide(color: Colors.black),
)
: Border.all(color: Colors.black)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Center(
child: child ?? const SizedBox(),
),
),
),
);
}
}
class MenuWidget extends StatelessWidget {
const MenuWidget({
super.key,
this.width,
this.onTap,
});
final double? width;
final ValueChanged<int>? onTap;
@override
Widget build(BuildContext context) {
return Container(
width: width ?? 200,
height: 300,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
border: Border(
right: BorderSide(color: Colors.black),
left: BorderSide(color: Colors.black),
bottom: BorderSide(color: Colors.black),
),
),
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () => onTap?.call(index),
child: Text('$index'),
);
},
),
);
}
}
class CustomDropDown extends StatefulWidget {
const CustomDropDown({
super.key,
});
@override
State<StatefulWidget> createState() => CustomDropDownState();
}
class CustomDropDownState extends State<CustomDropDown> {
final _tooltipController = ListenOverlayPortalController();
final _link = LayerLink();
/// width of the button after the widget rendered
double? _buttonWidth;
void onTap() {
_buttonWidth = context.size?.width;
_tooltipController.toggle();
}
int? _selectedItem;
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _link,
child: OverlayPortal(
controller: _tooltipController,
overlayChildBuilder: (BuildContext context) {
return CompositedTransformFollower(
link: _link,
targetAnchor: Alignment.bottomLeft,
child: Align(
alignment: AlignmentDirectional.topStart,
child: MenuWidget(
width: _buttonWidth,
onTap: (index) {
print('Selected element is $index');
_tooltipController.toggle();
setState(() => _selectedItem = index);
},
),
),
);
},
child: ListenableBuilder(
listenable: _tooltipController,
builder: (context, child) => ButtonWidget(
isExpanded: _tooltipController.isShowing,
onTap: onTap,
child: _tooltipController.isShowing
? const Text('Select an Item')
: Text(_selectedItem != null
? 'Item Selected is $_selectedItem'
: 'Tap to Select'),
),
),
),
);
}
}
class ListenOverlayPortalController extends OverlayPortalController
with ChangeNotifier {
@override
void show() {
super.show();
notifyListeners();
}
@override
void hide() {
super.hide();
notifyListeners();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment