Skip to content

Commit 46cce1c

Browse files
committed
GFSearchBar component completed
1 parent 5a2f656 commit 46cce1c

File tree

4 files changed

+85
-62
lines changed

4 files changed

+85
-62
lines changed

example/lib/main.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,16 @@ class _MyHomePageState extends State<MyHomePage>
177177
children: <Widget>[
178178

179179
GFSearchBar(
180-
dataList: list,
180+
searchList: list,
181181
hideSearchBoxWhenItemSelected: false,
182-
listContainerHeight: 100.0,
183-
queryBuilder: (query, list) {
182+
overlaySearchListHeight: 100.0,
183+
searchQueryBuilder: (query, list) {
184184
return list
185185
.where((item) =>
186186
item.toLowerCase().contains(query.toLowerCase()))
187187
.toList();
188188
},
189-
popupListItemBuilder: (item) {
189+
overlaySearchListItemBuilder: (item) {
190190
return Container(
191191
padding: const EdgeInsets.all(12),
192192
child: Text(
@@ -1168,6 +1168,10 @@ class _MyHomePageState extends State<MyHomePage>
11681168
),
11691169
),
11701170
],
1171+
shape: RoundedRectangleBorder(
1172+
borderRadius: BorderRadius.only(
1173+
topLeft: Radius.circular(24.0),
1174+
topRight: Radius.circular(24.0))),
11711175
indicatorColor: Colors.white,
11721176
// indicatorSize: TabBarIndicatorSize.label,
11731177
labelColor: Colors.lightGreen,

lib/components/search_bar/gf_search_bar.dart

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,36 @@ typedef QueryBuilder<T> = List<T> Function(
99

1010
class GFSearchBar<T> extends StatefulWidget {
1111
const GFSearchBar({
12-
@required this.dataList,
13-
@required this.popupListItemBuilder,
14-
@required this.queryBuilder,
12+
@required this.searchList,
13+
@required this.overlaySearchListItemBuilder,
14+
@required this.searchQueryBuilder,
1515
Key key,
1616
this.onItemSelected,
1717
this.hideSearchBoxWhenItemSelected = false,
18-
this.listContainerHeight,
18+
this.overlaySearchListHeight,
1919
this.noItemsFoundWidget,
2020
}) : super(key: key);
2121

22-
final List<T> dataList;
23-
final QueryListItemBuilder<T> popupListItemBuilder;
22+
23+
/// List of [text] or [widget] reference for users
24+
final List<T> searchList;
25+
26+
/// defines how the [searchList] items look like in overlayContainer
27+
final QueryListItemBuilder<T> overlaySearchListItemBuilder;
28+
29+
/// if true, it will hide the [searchBox]
2430
final bool hideSearchBoxWhenItemSelected;
25-
final double listContainerHeight;
26-
final QueryBuilder<T> queryBuilder;
31+
32+
/// defines the height of [searchList] overlay container
33+
final double overlaySearchListHeight;
34+
35+
/// can search and filter the [searchList]
36+
final QueryBuilder<T> searchQueryBuilder;
37+
38+
/// displays the [widget] when the search item failed
2739
final Widget noItemsFoundWidget;
2840

41+
/// defines what to do with onSelect [SearchList] item
2942
final OnItemSelected<T> onItemSelected;
3043

3144
@override
@@ -35,15 +48,15 @@ class GFSearchBar<T> extends StatefulWidget {
3548
class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
3649
final _controller = TextEditingController();
3750
List<T> _list;
38-
List<T> _tempList;
51+
List<T> _searchList;
3952
bool isFocused;
4053
FocusNode _focusNode;
4154
ValueNotifier<T> notifier;
4255
bool isRequiredCheckFailed;
43-
Widget textField;
44-
OverlayEntry overlayEntry;
56+
Widget searchBox;
57+
OverlayEntry overlaySearchList;
4558
bool showTextBox = false;
46-
double listContainerHeight;
59+
double overlaySearchListHeight;
4760
final LayerLink _layerLink = LayerLink();
4861
final double textBoxHeight = 48;
4962
final TextEditingController textController = TextEditingController();
@@ -55,62 +68,62 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
5568
}
5669

5770
void init() {
58-
_tempList = <T>[];
71+
_searchList = <T>[];
5972
notifier = ValueNotifier(null);
6073
_focusNode = FocusNode();
6174
isFocused = false;
62-
_list = List<T>.from(widget.dataList);
63-
_tempList.addAll(_list);
75+
_list = List<T>.from(widget.searchList);
76+
_searchList.addAll(_list);
6477
_focusNode.addListener(() {
6578
if (!_focusNode.hasFocus) {
6679
_controller.clear();
67-
if (overlayEntry != null) {
68-
overlayEntry.remove();
80+
if (overlaySearchList != null) {
81+
overlaySearchList.remove();
6982
}
70-
overlayEntry = null;
83+
overlaySearchList = null;
7184
} else {
72-
_tempList
85+
_searchList
7386
..clear()
7487
..addAll(_list);
75-
if (overlayEntry == null) {
76-
onTextfieldFocus();
88+
if (overlaySearchList == null) {
89+
onTextFieldFocus();
7790
} else {
78-
overlayEntry.markNeedsBuild();
91+
overlaySearchList.markNeedsBuild();
7992
}
8093
}
8194
});
8295
_controller.addListener(() {
8396
final text = _controller.text;
8497
if (text.trim().isNotEmpty) {
85-
_tempList.clear();
86-
final filterList = widget.queryBuilder(text, widget.dataList);
98+
_searchList.clear();
99+
final filterList = widget.searchQueryBuilder(text, widget.searchList);
87100
if (filterList == null) {
88101
throw Exception(
89102
"List cannot be null",
90103
);
91104
}
92-
_tempList.addAll(filterList);
93-
if (overlayEntry == null) {
94-
onTextfieldFocus();
105+
_searchList.addAll(filterList);
106+
if (overlaySearchList == null) {
107+
onTextFieldFocus();
95108
} else {
96-
overlayEntry.markNeedsBuild();
109+
overlaySearchList.markNeedsBuild();
97110
}
98111
} else {
99-
_tempList
112+
_searchList
100113
..clear()
101114
..addAll(_list);
102-
if (overlayEntry == null) {
103-
onTextfieldFocus();
115+
if (overlaySearchList == null) {
116+
onTextFieldFocus();
104117
} else {
105-
overlayEntry.markNeedsBuild();
118+
overlaySearchList.markNeedsBuild();
106119
}
107120
}
108121
});
109122
}
110123

111124
@override
112125
void didUpdateWidget(GFSearchBar oldWidget) {
113-
if (oldWidget.dataList != widget.dataList) {
126+
if (oldWidget.searchList != widget.searchList) {
114127
init();
115128
}
116129
super.didUpdateWidget(oldWidget);
@@ -119,9 +132,9 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
119132
@override
120133
Widget build(BuildContext context) {
121134

122-
listContainerHeight =
123-
widget.listContainerHeight ?? MediaQuery.of(context).size.height / 4;
124-
textField = Padding(
135+
overlaySearchListHeight =
136+
widget.overlaySearchListHeight ?? MediaQuery.of(context).size.height / 4;
137+
searchBox = Padding(
125138
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
126139
child: TextField(
127140
controller: _controller,
@@ -160,18 +173,18 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
160173
else
161174
CompositedTransformTarget(
162175
link: _layerLink,
163-
child: textField,
176+
child: searchBox,
164177
),
165178
],
166179
);
167180
return column;
168181
}
169182

170-
void onDropDownItemSelected(T item) {
171-
if (overlayEntry != null) {
172-
overlayEntry.remove();
183+
void onSearchListItemSelected(T item) {
184+
if (overlaySearchList != null) {
185+
overlaySearchList.remove();
173186
}
174-
overlayEntry = null;
187+
overlaySearchList = null;
175188
_controller.clear();
176189
_focusNode.unfocus();
177190
setState(() {
@@ -184,24 +197,24 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
184197
}
185198
}
186199

187-
void onTextfieldFocus() {
188-
final RenderBox textFieldRenderBox = context.findRenderObject();
200+
void onTextFieldFocus() {
201+
final RenderBox searchBoxRenderBox = context.findRenderObject();
189202
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
190-
final width = textFieldRenderBox.size.width;
203+
final width = searchBoxRenderBox.size.width;
191204
final position = RelativeRect.fromRect(
192205
Rect.fromPoints(
193-
textFieldRenderBox.localToGlobal(
194-
textFieldRenderBox.size.topLeft(Offset.zero),
206+
searchBoxRenderBox.localToGlobal(
207+
searchBoxRenderBox.size.topLeft(Offset.zero),
195208
ancestor: overlay,
196209
),
197-
textFieldRenderBox.localToGlobal(
198-
textFieldRenderBox.size.topRight(Offset.zero),
210+
searchBoxRenderBox.localToGlobal(
211+
searchBoxRenderBox.size.topRight(Offset.zero),
199212
ancestor: overlay,
200213
),
201214
),
202215
Offset.zero & overlay.size,
203216
);
204-
overlayEntry = OverlayEntry(
217+
overlaySearchList = OverlayEntry(
205218
builder: (context) {
206219
final height = MediaQuery.of(context).size.height;
207220
return Positioned(
@@ -210,22 +223,22 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
210223
child: CompositedTransformFollower(
211224
offset: Offset(
212225
0,
213-
height - position.bottom < listContainerHeight
226+
height - position.bottom < overlaySearchListHeight
214227
? (textBoxHeight + 6.0)
215-
: -(listContainerHeight - 8.0),
228+
: -(overlaySearchListHeight - 8.0),
216229
),
217230
showWhenUnlinked: false,
218231
link: _layerLink,
219232
child: Container(
220-
height: listContainerHeight,
233+
height: overlaySearchListHeight,
221234
margin: const EdgeInsets.symmetric(horizontal: 12),
222235
child: Card(
223236
color: Colors.white,
224237
elevation: 5,
225238
shape: const RoundedRectangleBorder(
226239
borderRadius: BorderRadius.all(Radius.circular(4)),
227240
),
228-
child: _tempList.isNotEmpty
241+
child: _searchList.isNotEmpty
229242
? Scrollbar(
230243
child: ListView.separated(
231244
padding: const EdgeInsets.symmetric(vertical: 4),
@@ -235,13 +248,13 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
235248
itemBuilder: (context, index) => Material(
236249
color: Colors.transparent,
237250
child: InkWell(
238-
onTap: () => onDropDownItemSelected(_tempList[index]),
239-
child: widget.popupListItemBuilder(
240-
_tempList.elementAt(index),
251+
onTap: () => onSearchListItemSelected(_searchList[index]),
252+
child: widget.overlaySearchListItemBuilder(
253+
_searchList.elementAt(index),
241254
),
242255
),
243256
),
244-
itemCount: _tempList.length,
257+
itemCount: _searchList.length,
245258
),
246259
)
247260
: widget.noItemsFoundWidget != null
@@ -257,7 +270,7 @@ class MySingleChoiceSearchState<T> extends State<GFSearchBar<T>> {
257270
);
258271
},
259272
);
260-
Overlay.of(context).insert(overlayEntry);
273+
Overlay.of(context).insert(overlaySearchList);
261274
}
262275
}
263276

lib/components/tabs/gf_tabBar.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class GFTabBar extends StatefulWidget {
156156
/// will be used.
157157
final TabController controller;
158158

159+
/// defines the shape of tabBar
159160
final ShapeBorder shape;
160161

161162
@override

lib/components/tabs/gf_tabs.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class GFTabs extends StatefulWidget {
3838
this.tabs,
3939
this.controller,
4040
this.tabBarHeight,
41+
this.shape,
4142
}) : assert(length != null && length >= 0),
4243
assert(initialIndex != null &&
4344
initialIndex >= 0 &&
@@ -169,6 +170,9 @@ class GFTabs extends StatefulWidget {
169170
/// defines the tabBar height
170171
final double tabBarHeight;
171172

173+
/// defines the shape of tabBar
174+
final ShapeBorder shape;
175+
172176
@override
173177
_GFTabsState createState() => _GFTabsState();
174178
}
@@ -187,6 +191,7 @@ class _GFTabsState extends State<GFTabs> {
187191
child: Column(
188192
children: <Widget>[
189193
GFTabBar(
194+
shape: widget.shape,
190195
length: widget.length,
191196
initialIndex: widget.initialIndex,
192197
tabBarHeight: widget.tabBarHeight,

0 commit comments

Comments
 (0)