搜索词联想功能实现方案
需求背景:
实现搜索框的搜索联想功能,当输入框输入字符时候,立刻进行网络请求,将相关推荐展示在下方的列表中,要求每次展示的一定是当前最新输入的内容的推荐词。
实现思路:
展示搜索词联想需要满足如下三点:
1.搜索框输入内容发生变化需要立刻进行网络请求搜索关联词汇
2.搜索词输入内容变化较快,需要保证每次展示出来的联想词都是输入框中最新的输入内容所对应的联想词
3.每当有新的请求时候需要及时取消掉现在正在进行的网络请求,以免网络资源浪费。
综合以上三点,发现网上上一些现有方案都是基于RXjava实现,在一个外文培训网站发现一个较为完善的方案,通过debounce、filter、distinctUntilChanged、switchMap几个方法来满足以上三点功能,这样原本复杂的逻辑处理就通过简单的几个链式调用完成了,修改补充后的代码如下:
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!TextUtils.isEmpty(editText.getText().toString())) {
if(publishSubject!=null) {
publishSubject.onNext(editText.getText().toString());
}
}
}
});
private void initPublishSubject() {
compositeDisposable = new CompositeDisposable();
publishSubject = PublishSubject.create();
Disposable disposable = publishSubject.debounce(300, TimeUnit.MILLISECONDS)
.filter(new Predicate<String>() {
@Override
public boolean test(String s) throws Exception {
if(TextUtils.isEmpty(s)){
return false;
} else {
return true;
}
}
})
.distinctUntilChanged()
.switchMap(new Function<String, ObservableSource<ArrayList<String>>>() {
@Override
public ObservableSource<ArrayList<String>> apply(String s) throws Exception {
return getSearchObservable(s);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(Object o) throws Exception {
ArrayList<String> list = (ArrayList<String>)o;
if(list!=null && list.size()!=0) {
//将返回的联想词展示到列表中
}
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
MyLog.d(TAG,"fail:" + throwable.getMessage());
}
});
compositeDisposable.add(disposable);
}
private Observable<ArrayList<String>> getSearchObservable(final String query) {
return Observable.create(new ObservableOnSubscribe<ArrayList<String>>() {
@Override
public void subscribe(ObservableEmitter<ArrayList<String>> observableEmitter) throws Exception {
ArrayList<String> list = new ArrayList<>();
try {
//通过网络获取联想词,将其赋值给list列表
} catch (Exception e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
observableEmitter.onNext(list);
}
}).subscribeOn(Schedulers.io());
}
得到搜索联想词之后就是将其填充到recycleview中,这一步较为简单这里不再详述,主要对以上代码中使用的主要RXjava操作符简单解析如下:
1.Debounce
仅在过了一段指定的时间后还没有发射数据时才发射一个数据主要用于过滤掉发射速率太快的数据,比如以上代码中,如果首次输入了a,然后在指定的300毫秒时间间隔内,有输入了b,此时就会过滤掉数据a,重新以ab为开始,重新等待300毫秒,如果这300毫秒内没有新的输入,就发射数据ab,如果有新的输入比如c,就过滤掉数据ab,以数据abc为新的数据继续等待300毫秒,如果300毫秒时间到了,没有新的输入,就发射数据abc,这样的好处是过滤掉了一些中间不必要的数据。
2.Filter:
使用一个指定的函数测试数据,只有通过测试的数据才能够发射,比如以上代码中,只有非空的数据才能够发射,空数据则过滤掉
3.DistinctUntilChanged:
DistinctUntilChanged与distinct操作符类似,功能都是避免重复,只允许通过未发射过的数据,假设最后一个请求的搜索词是abc,然后用户又马上删除了C,然后又再次输入一个c这样搜索词依然是abc,这样最后的这个abc的请求就会过滤掉。
4.SwitchMap:
switchMap运算符用于避免多余的网络呼叫结果,该结果对于显示给用户而言并不需要更多,只需要最新一次的结果即可。假设最后输入的是ab,如果正在进行ab的联想词请求时候用户又继续输入了c,此时用户只对abc的联想词感兴趣,而不再需要ab的联想词,switchMap正好可以解决这个问题,它仅返回最新的搜索结果而忽略掉之前在进行的请求。