Ok, So I have made my Custom searchdropdown in flutter. I wanted to make it reusable widget, so I made it in different file. Now, I want that when I tap on outside of my dropdown, my overlay closes. I tried with gesture detector, but I couldn’t get the job done. Here is my customsearchdropdown widget :
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}
class CustomSearchableDropDown extends StatefulWidget {
final List<AcademicData> originalList;
final Function(AcademicData)? onTap;
const CustomSearchableDropDown(
{super.key,
required this.originalList,
this.onTap,
});
@override
State<CustomSearchableDropDown> createState() =>
_CustomSearchableDropDownState();
}
class _CustomSearchableDropDownState extends State<CustomSearchableDropDown> {
TextEditingController controller = TextEditingController();
ValueNotifier<List<AcademicData>> list = ValueNotifier([]);
AcademicData? _selectedFinancialYear;
OverlayEntry? _overlayEntry;
final GlobalKey _key=GlobalKey();
ValueNotifier<bool> show = ValueNotifier(false);
double getHeight(entries) {
if(entries==1) {
return 48;
}
else if(entries==2) {
return 112;
}
else if(entries==3) {
return ScreenSize.utilHeight * 0.2;
}
else {
return 224;
}
}
OverlayEntry _createOverlayEntry() {
Rect? bounds = _key.globalPaintBounds;
return OverlayEntry(
builder: (context) => Positioned(
left: bounds?.left,
top: (bounds?.top ?? 0) + 60 + 5,
width: bounds?.width,
child: Material(
elevation: 4.0,
child: SizedBox(
child: ValueListenableBuilder(
valueListenable: list,
builder: (context, myList, child) {
return SizedBox(
height: getHeight(myList.length),
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: myList.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
_selectedFinancialYear = myList[index];
show.value = false;
_overlayEntry?.remove();
widget.onTap!(myList[index]);
controller.clear();
list.value=widget.originalList;
},
child: ListTile(
title: Text(
myList[index].sessionYear ?? "",
style: const TextStyle(color: Colors.black),
),
),
);
},
),
);
},
),
),
),
),
);
}
void _filterResults(String query) {
List<AcademicData> duplicateItems = [];
duplicateItems = widget.originalList.where((item) {
if (item.sessionYear != null && item.sessionYear!.contains(query)) {
return true;
}
return false;
}).toList();
list.value = duplicateItems;
debugPrint("Changed");
for (var v in list.value) {
debugPrint(v.sessionYear);
}
}
final FocusNode _focusNode = FocusNode();
@override
void initState() {
list.value = widget.originalList;
if (widget.originalList.isNotEmpty) {
_selectedFinancialYear = widget.originalList[0];
}
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
_overlayEntry = _createOverlayEntry();
debugPrint("Inside");
if (_overlayEntry != null) {
debugPrint("Not null");
Overlay.of(context).insert(_overlayEntry!);
}
} else {
_overlayEntry?.remove();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: show,
builder: (context, val, child) {
return val == false
? InkWell(
onTap: () {
show.value = true;
//_createOverlayEntry();
},
child: SizedBox(
height: 52,
width: double.infinity,
child: InputDecorator(
decoration: InputDecoration(
labelText: 'Academic Year',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_selectedFinancialYear?.sessionYear ?? "No Data",
),
const Icon(
Icons.arrow_drop_down,
),
],
),
),
),
)
: SizedBox(
height: 56,
width: double.infinity,
child: TextField(
key: _key,
controller: controller,
focusNode: _focusNode,
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
),
labelText: "Academic Year",
),
onChanged: (value) {
_filterResults(value);
},
onSubmitted: (value) {
show.value=false;
},
),
);
},
);
}
}
And I am using it like this in my screen:
Expanded(
child: CustomSearchableDropDown(
originalList: userP.academicYear??[],
onTap: (value) {
setState(() {
_selectedFinancialYear=value;
});
},
),
),
In my screen, I have used gesture detector to detect when someone taps outside, can it be helpful in this case?
2
Answers
My people, I found a solution for this. I just wrapped my Material widget (which is in OverlayEntry) inside if a TapRegion and called onTapOutside. It was that simple.
Here is the code change:
And one more thing, if someone else is using
InputDecorator
, try to avoid using child and row that I used. When I was decreasing my height forSizedBox
, the text was becoming Invisible. So, instead I usedprefix
andsuffixIcon
for the same. Here is the better version code:I have still kept the commented code so you can understand what I was trying to do.
https://drive.google.com/file/d/1Ca_gH73KzLn5tJifbTfnZVBLqtHsqe0L/view?usp=sharing
download all File and put your Project and check