【Flutter】设计一个简单的列表搜索栏

2,183 阅读3分钟

在一些类似微信,微博等大型应用中,它们基本上都会自带一个搜索功能。在之前,我使用了 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();
  }); 
}

小结

本篇主要讲解了如何设计搜索列表,基于流或者响应式的设计,可以帮助我们快速实现一些数据和操作的过滤。