Android仿淘寶搜索聯(lián)想功能的示例代碼
現(xiàn)在不少應(yīng)用都提供了搜索功能,有些還提供了搜索聯(lián)想。對(duì)于一個(gè)搜索聯(lián)想功能,最基本的實(shí)現(xiàn)流程為:客戶端通過(guò)監(jiān)聽(tīng)輸入框內(nèi)容的變化,當(dāng)輸入框發(fā)生變化之后就會(huì)回調(diào)afterTextChanged方法,客戶端利用當(dāng)前輸入框內(nèi)的文字向服務(wù)器發(fā)起請(qǐng)求,服務(wù)器返回與該搜索文字關(guān)聯(lián)的結(jié)果給客戶端進(jìn)行展示。服務(wù)器那邊,一般要做內(nèi)存緩存池,就是把有可能的結(jié)果都放在內(nèi)存中。
效果圖

APP這邊也有幾個(gè)重要的問(wèn)題需要我們思考
- 當(dāng)搜索詞為空時(shí),不應(yīng)該發(fā)起網(wǎng)絡(luò)請(qǐng)求。
- 在用戶連續(xù)輸入的情況下,可能會(huì)發(fā)起某些不必要的請(qǐng)求。例如用戶輸入了abc,那么按照上面的實(shí)現(xiàn),客戶端就會(huì)發(fā)起a、ab、abc三個(gè)請(qǐng)求。
- 如果用戶依次輸入了ab和abc,那么首先會(huì)發(fā)起關(guān)鍵詞為ab請(qǐng)求,之后再發(fā)起abc的請(qǐng)求,但是abc的請(qǐng)求如果先于ab的請(qǐng)求返回,那么就會(huì)造成用戶期望搜索的結(jié)果為abc,但是我們最終希望得到的結(jié)果卻是和ab關(guān)聯(lián)的。
我的方案是采用retrofit2+rxjava2來(lái)實(shí)現(xiàn)的,針對(duì)這幾個(gè)問(wèn)題的大致思路如下,關(guān)于這幾個(gè)操作符的解釋,在Demo中有較完整的解釋
- 使用debounce操作符,當(dāng)輸入框發(fā)生變化時(shí),不會(huì)立刻將事件發(fā)布出去,而是等待200ms,如果在這段事件內(nèi),輸入框沒(méi)有發(fā)生變化,那么才發(fā)送該事件;反之,則在收到新的關(guān)鍵詞后,繼續(xù)等待200ms。
- 使用filter操作符,只有關(guān)鍵詞的長(zhǎng)度大于0時(shí)才把事件發(fā)布出去。filter作用:對(duì)源Observable產(chǎn)生的結(jié)果按照指定條件進(jìn)行過(guò)濾,只有滿足條件的結(jié)果才會(huì)提交給訂閱者
- 使用switchMap操作符,這樣當(dāng)發(fā)起了abc的請(qǐng)求之后,即使ab的結(jié)果返回了,也不會(huì)發(fā)送給下游,從而避免了出現(xiàn)前面介紹的搜索詞和聯(lián)想結(jié)果不匹配的問(wèn)題。switchMap操作符會(huì)保存最新的Observable產(chǎn)生的結(jié)果而舍棄舊的結(jié)果。
下面貼出關(guān)鍵代碼
private void initEdt() {
editText = (EditText) findViewById(R.id.edt);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().trim().isEmpty()) {
mPop.dismiss();
} else {
//輸入內(nèi)容非空的時(shí)候才開(kāi)始搜索
startSearch(s.toString());
}
}
});
mPublishSubject = PublishSubject.create();
mPublishSubject.debounce(200, TimeUnit.MILLISECONDS) //這里我們限制只有在輸入字符200毫秒后沒(méi)有字符沒(méi)有改變時(shí)才去請(qǐng)求網(wǎng)絡(luò),節(jié)省了資源
.filter(new Predicate<String>() { //對(duì)源Observable產(chǎn)生的結(jié)果按照指定條件進(jìn)行過(guò)濾,只有滿足條件的結(jié)果才會(huì)提交給訂閱者
@Override
public boolean test(String s) throws Exception {
//當(dāng)搜索詞為空時(shí),不發(fā)起請(qǐng)求
return s.length() > 0;
}
})
/**
* flatmap:把Observable產(chǎn)生的結(jié)果轉(zhuǎn)換成多個(gè)Observable,然后把這多個(gè)Observable
“扁平化”成一個(gè)Observable,并依次提交產(chǎn)生的結(jié)果給訂閱者
*concatMap:操作符flatMap操作符不同的是,concatMap操作符在處理產(chǎn)生的Observable時(shí),
采用的是“連接(concat)”的方式,而不是“合并(merge)”的方式,
這就能保證產(chǎn)生結(jié)果的順序性,也就是說(shuō)提交給訂閱者的結(jié)果是按照順序提交的,不會(huì)存在交叉的情況
*switchMap:與flatMap操作符不同的是,switchMap操作符會(huì)保存最新的Observable產(chǎn)生的
結(jié)果而舍棄舊的結(jié)果
**/
.switchMap(new Function<String, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(String query) throws Exception {
return getSearchObservable(query);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<String>() {
@Override
public void onNext(String s) {
//顯示搜索聯(lián)想的結(jié)果
showSearchResult(s);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
});
mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(mCompositeDisposable);
}
//開(kāi)始搜索
private void startSearch(String query) {
mPublishSubject.onNext(query);
}
private Observable<String> getSearchObservable(final String query) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
Log.d(TAG, "開(kāi)始請(qǐng)求,關(guān)鍵詞為:" + query);
try {
Thread.sleep(100); //模擬網(wǎng)絡(luò)請(qǐng)求,耗時(shí)100毫秒
} catch (InterruptedException e) {
if (!observableEmitter.isDisposed()) {
observableEmitter.onError(e);
}
}
if (!(query.contains("科") || query.contains("耐") || query.contains("七"))) {
//沒(méi)有聯(lián)想結(jié)果,則關(guān)閉pop
mPop.dismiss();
return;
}
Log.d("SearchActivity", "結(jié)束請(qǐng)求,關(guān)鍵詞為:" + query);
observableEmitter.onNext(query);
observableEmitter.onComplete();
}
}).subscribeOn(Schedulers.io());
}
下面是針對(duì)幾個(gè)操作符,從官網(wǎng)download下來(lái)的東西,供大家一起學(xué)習(xí)
debounce

debounce原理類似于我們?cè)谑盏秸?qǐng)求之后,發(fā)送一個(gè)延時(shí)消息給下游,如果在這段延時(shí)時(shí)間內(nèi)沒(méi)有收到新的請(qǐng)求,那么下游就會(huì)收到該消息;而如果在這段延時(shí)時(shí)間內(nèi)收到來(lái)新的請(qǐng)求,那么就會(huì)取消之前的消息,并重新發(fā)送一個(gè)新的延時(shí)消息,以此類推。
而如果在這段時(shí)間內(nèi),上游發(fā)送了onComplete消息,那么即使沒(méi)有到達(dá)需要等待的時(shí)間,下游也會(huì)立刻收到該消息。
filter

filter的原理很簡(jiǎn)單,就是傳入一個(gè)Predicate函數(shù),其參數(shù)為上游發(fā)送的事件,只有該函數(shù)返回true時(shí),才會(huì)將事件發(fā)送給下游,否則就丟棄該事件。
switchMap

switchMap的原理是將上游的事件轉(zhuǎn)換成一個(gè)或多個(gè)新的Observable,但是有一點(diǎn)很重要,就是如果在該節(jié)點(diǎn)收到一個(gè)新的事件之后,那么如果之前收到的時(shí)間所產(chǎn)生的Observable還沒(méi)有發(fā)送事件給下游,那么下游就再也不會(huì)收到它發(fā)送的事件了。
如上圖所示,該節(jié)點(diǎn)先后收到了紅、綠、藍(lán)三個(gè)事件,并將它們映射成為紅一、紅二、綠一、綠二、藍(lán)一、藍(lán)二,但是當(dāng)藍(lán)一發(fā)送完事件時(shí),綠二依舊沒(méi)有發(fā)送事件,而最初綠色事件在藍(lán)色事件之前,那么綠二就不會(huì)發(fā)送給下游。
- flatmap:把Observable產(chǎn)生的結(jié)果轉(zhuǎn)換成多個(gè)Observable,然后把這多個(gè)Observable“扁平化”成一個(gè)Observable,并依次提交產(chǎn)生的結(jié)果給訂者
- concatMap:flatMap操作符不同的是,concatMap操作符在處理產(chǎn)生的Observable時(shí),采用的是“連接(concat)”的方式,而不是“合并(merge)”的方式,這就能保證產(chǎn)生結(jié)果的順序性,也就是說(shuō)提交給訂閱者的結(jié)果是按照順序提交的,不會(huì)存在交叉的情況
- switchMap:與flatMap操作符不同的是,switchMap操作符會(huì)保存最新的Observable產(chǎn)生的結(jié)果而舍棄舊的結(jié)果
GitHub地址(完整Demo,歡迎下載)
https://github.com/zhouxu88/SearchDemo
rxjava2學(xué)習(xí)地址
https://github.com/ReactiveX/RxJava
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android畫(huà)板開(kāi)發(fā)之添加背景和保存畫(huà)板內(nèi)容為圖片
這篇文章主要為大家詳細(xì)介紹了Android畫(huà)板開(kāi)發(fā)之添加背景和保存畫(huà)板內(nèi)容為圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android 中自定義ContentProvider與ContentObserver的使用簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 中自定義ContentProvider與ContentObserver的使用簡(jiǎn)單實(shí)例的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-09-09
Android Intent發(fā)送廣播消息實(shí)例詳解
這篇文章主要介紹了Android Intent發(fā)送廣播消息實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04
Flutter移動(dòng)端進(jìn)行多渠道打包發(fā)布的全過(guò)程
在使用flutter開(kāi)發(fā)的過(guò)程中,需要根據(jù)不同的環(huán)境,不同的包名來(lái)打包,下面這篇文章主要給大家介紹了關(guān)于Flutter移動(dòng)端進(jìn)行多渠道打包發(fā)布的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Android編程基于自定義控件實(shí)現(xiàn)時(shí)鐘功能的方法
這篇文章主要介紹了Android編程基于自定義控件實(shí)現(xiàn)時(shí)鐘功能的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android自定義控件的定義及時(shí)鐘功能相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-03-03
Android 自定義view模板并實(shí)現(xiàn)點(diǎn)擊事件的回調(diào)
這篇文章主要介紹了Android 自定義view模板并實(shí)現(xiàn)點(diǎn)擊事件的回調(diào)的相關(guān)資料,需要的朋友可以參考下2017-01-01
如何調(diào)用百度地圖API實(shí)現(xiàn)手機(jī)自動(dòng)定位
api手機(jī)自動(dòng)定位,通過(guò)聲明地址解析器,獲取當(dāng)前坐標(biāo),如何調(diào)用百度地圖api實(shí)現(xiàn)手機(jī)自動(dòng)定位呢?接下來(lái),一起跟小編來(lái)學(xué)習(xí)吧。2015-09-09
基于Android實(shí)現(xiàn)隨手指移動(dòng)的ImageView
這篇文章主要介紹了基于Android實(shí)現(xiàn)隨手指移動(dòng)的ImageView的相關(guān)資料,需要的朋友可以參考下2016-01-01
Android集成GreenDao數(shù)據(jù)庫(kù)的操作步驟
這篇文章主要介紹了Android集成GreenDao數(shù)據(jù)庫(kù),使用數(shù)據(jù)庫(kù)存儲(chǔ)時(shí)候,一般都會(huì)使用一些第三方ORM框架,比如GreenDao,本文分幾步給大家介紹Android集成GreenDao數(shù)據(jù)庫(kù)的方法,需要的朋友可以參考下2022-10-10
Flutter實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的方法示例
這篇文章主要介紹了Flutter實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03

