在一些类似微信,微博等大型应用中,它们基本上都会自带一个搜索功能。在之前,我使用了 RxDart 设计了一个单选列表组件(原文在这里),下面我将使用这个单选列表组件,来构建一个满足部分需求的列表搜索。
TextField
在 TextField 中,我们可以传入 TextEditingController 来获取实时的用户输入。TextEditingController 实际上就是一个既可以监听,也可以观察的对象。通过这个对象我们可以获得 TextField 的状态,从而控制它。
然而很遗憾, TextEditingController 并不是流对象,即便它表现的像 BehaviorSubject 。所以,现在我的目标是把 TextField 用 RxDart 控制。TextField 提供了 onChanged 这个方法,通过这个回调函数,我可以拿到 TextField 变更后的状态。
final controller = BehaviorSubject.seed('');
@override
Widget build(BuildContext context) {
return ...
TextField(
...
controller: TextEditingController(text: controller.value),
onChanged: (value) {
controller.add(value);
},
);
}
这样,就完成我们的第一步。
添加数据列表
在这里,我使用我之前用过的 SingleCheckListView,为了让它能够从流中获得数据,我使用了 StreamBuilder 。
class SearchingList<T> extends StatefulWidget {
final Stream<List<T>> dataStream;
...
}
class _SearchingListState<T> extends State<SearchingList<T>> {
final controller = BehaviorSubject.seed('');
@override
Widget build(BuildContext context) {
return Column(
child: [
TextField(...),
StreamBuilder<List<T>>(
stream: dataStream,
builder: (context, snapshot) {
return SingleCheckListView<T>(
items: snapshot.data,
...
);
}
),
],
)
}
}
外部传入流数据后,StreamBuilder 便会自动订阅,并通过 SingleCheckListView 展示数据。
列表搜索
我们需要实现的是列表的搜索功能,当用户输入内容的时候,需要根据这些内容来匹配列表中的数据。既然我们有流,那么便可以使用 Rx.combineLatest2 这个流构造器来对让用户输入内容和数据进行匹配。
然而我并不知道列表中的数据结构是怎么样的,所以需要提供一个 predicate 函数,让调用者去实现这个过滤条件。
PS:combineLatest 这个操作符,它可以把不同的流的最新数据都汇总到一起。
class SearchingList<T> extends StatefulWidget {
final Stream<List<T>> dataStream;
final bool Function(String input, T item) predicate;
...
}
class _SearchingListState<T> extends State<SearchingList<T>> {
final controller = BehaviorSubject.seed('');
@override
Widget build(BuildContext context) {
...
StreamBuilder(
stream: _filtratedData(widget.dataStream, controller, widget.predicate)
)
}
Stream<List<T>> _filtratedData(
Stream<List<T>> data$,
Stream<String> input$,
bool Function(String input, T item) predicate,
) {
// 看这里,主要的实现这样的。
return Rx.combineLatest2(data$, input$, (a, b) => [a, b]).map((event) {
final items = event[0] as List<T> ?? [];
final tag = event[1] as String;
if (isNullOrEmpty(tag)) {
return items;
}
return items.where((item) => predicate(tag, item)).toList();
});
}
}
搜索防抖
到这一步就完了吗?如果每次输入都会进行过滤,肯定会浪费运算资源,因为不是每次输入的都是有效的。为了节省这种运算资源,我们可以使用函数防抖,来获取最近一次的有效输入。所谓函数防抖,就是将执行多次的函数变成只执行最后一次的函数。在这种情况下,我们默认当用户正在期待有效结果的时候,他此时的输入才是有效的。
那么,怎么为流设计防抖,只需要让输入流进行 debounce。
Stream<List<T>> _filtratedData(
Stream<List<T>> data$,
Stream<String> input$,
bool Function(String input, T item) predicate,
) {
// 看这里,主要的实现这样的。
return Rx.combineLatest2(
data$,
input$.debounceTime(const Duration(seconed: 1)),
(a, b) => [a, b],
).map((event) {
final items = event[0] as List<T> ?? [];
final tag = event[1] as String;
if (isNullOrEmpty(tag)) {
return items;
}
return items.where((item) => predicate(tag, item)).toList();
});
}
小结
本篇主要讲解了如何设计搜索列表,基于流或者响应式的设计,可以帮助我们快速实现一些数据和操作的过滤。