這兩天閑來(lái)無(wú)事,就把前短時(shí)間項(xiàng)目中的搜索功能抽取出來(lái)狰闪,重新寫一下疯搅,搜索功能雖然簡(jiǎn)單,但是設(shè)計(jì)到得知識(shí)點(diǎn)也挺多的埋泵,就當(dāng)做一個(gè)總結(jié)吧幔欧。
代碼地址在最后面罪治,話不多說(shuō),上效果圖 _
歷史記錄的存儲(chǔ)
首先來(lái)說(shuō)下關(guān)于歷史記錄的存儲(chǔ)礁蔗,歷史記錄的存儲(chǔ)方式其實(shí)可以有很多方法觉义,可以用sp,數(shù)據(jù)庫(kù)等等浴井,那么就直接開擼吧晒骇。說(shuō)開擼你還真以為就直接開擼了,還是先想想吧滋饲,我們做歷史記錄存儲(chǔ)的時(shí)候需要提供什么給調(diào)用者厉碟,其實(shí)很簡(jiǎn)單無(wú)非就是可以增刪改查嗎。為了遵守里氏替換原則屠缭,就先寫了個(gè)抽象類BaseHistoryStorage箍鼓,里面有幾個(gè)抽象方法,至于是什么自己看吧呵曹。這樣不管你是用SP還是數(shù)據(jù)庫(kù)甚至其他更牛的技術(shù)款咖,只需要集成Base類,實(shí)現(xiàn)這些抽象方法就行了奄喂,至于你是怎么實(shí)現(xiàn)的铐殃,我才不管呢。
/**
* 歷史信息存儲(chǔ)基類
* Created by Zellerpooh on 17/1/18.
*/
public abstract class BaseHistoryStorage {
/**
* 保存歷史記錄時(shí)調(diào)用
*
* @param value
*/
public abstract void save(String value);
/**
* 移除單條歷史記錄
*
* @param key
*/
public abstract void remove(String key);
/**
* 清空歷史記錄
*/
public abstract void clear();
/**
* 生成key
*
* @return
*/
public abstract String generateKey();
/**
* 返回排序好的歷史記錄
*
* @return
*/
public abstract ArrayList<SearchHistoryModel> sortHistory();
}
上面的代碼很好理解跨新,SearchHistoryModel是我寫的一個(gè)JavaBean,里面就放了兩個(gè)String,一個(gè)是歷史搜索的內(nèi)容富腊,一個(gè)是歷史記錄的Key,其實(shí)你直接返回一個(gè)String泛型的ArrayList的就行域帐,但是我這里為了用SP實(shí)現(xiàn)的時(shí)候跟快速偷了個(gè)懶赘被,好了自己去實(shí)現(xiàn)一個(gè)歷史記錄存儲(chǔ)功能把。
通過(guò)SharedPreference實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)
聽到讓你自己去實(shí)現(xiàn)是不是心涼了一半肖揣,當(dāng)然是逗你的了民假,既然都來(lái)了怎么能不給你點(diǎn)福利呢,下面我就實(shí)現(xiàn)一個(gè)簡(jiǎn)單的通過(guò)SharedPreference實(shí)現(xiàn)的數(shù)據(jù)存儲(chǔ)吧龙优,來(lái)拋磚迎玉吧羊异。
private static SpHistoryStorage instance;
private SpHistoryStorage(Context context, int historyMax) {
this.context = context.getApplicationContext();
this.HISTORY_MAX = historyMax;
}
public static synchronized SpHistoryStorage getInstance(Context context, int historyMax) {
if (instance == null) {
synchronized (SpHistoryStorage.class) {
if (instance == null) {
instance = new SpHistoryStorage(context, historyMax);
}
}
}
return instance;
}
作為一個(gè)勵(lì)志成為高逼格的高級(jí)程序員的菜鳥,當(dāng)然不會(huì)放過(guò)任何裝逼的機(jī)會(huì)彤断,對(duì)于這種比較耗資源的數(shù)據(jù)存儲(chǔ)野舶,將他設(shè)計(jì)為單例模式當(dāng)然最合適不過(guò)了,上面就是一個(gè)簡(jiǎn)單的DCL的單例模式實(shí)現(xiàn),在安卓中將Context傳入到單例中是一個(gè)大忌瓦糟,你試想一下你的activity永遠(yuǎn)得不到釋放是一件多么恐怖的事情筒愚,所以我就換成了applicationContext,反正他是跟隨程序一直在的。
然后來(lái)看看里面的方法實(shí)現(xiàn)吧
private static SimpleDateFormat mFormat = new SimpleDateFormat("yyyyMMddHHmmss");
@Override
public String generateKey() {
return mFormat.format(new Date());
}
上面這個(gè)方法就是為了存儲(chǔ)的時(shí)候根據(jù)當(dāng)前時(shí)間生成的一個(gè)Key,用來(lái)判斷先后順序菩浙。
@Override
public ArrayList<SearchHistoryModel> sortHistory() {
Map<String, ?> allHistory = getAll();
ArrayList<SearchHistoryModel> mResults = new ArrayList<>();
Map<String, String> hisAll = (Map<String, String>) getAll();
//將key排序升序
Object[] keys = hisAll.keySet().toArray();
Arrays.sort(keys);
int keyLeng = keys.length;
//這里計(jì)算 如果歷史記錄條數(shù)是大于 可以顯示的最大條數(shù),則用最大條數(shù)做循環(huán)條件,防止歷史記錄條數(shù)-最大條數(shù)為負(fù)值劲蜻,數(shù)組越界
int hisLeng = keyLeng > HISTORY_MAX ? HISTORY_MAX : keyLeng;
for (int i = 1; i <= hisLeng; i++) {
mResults.add(new SearchHistoryModel((String) keys[keyLeng - i], hisAll.get(keys[keyLeng - i])));
}
return mResults;
}
public Map<String, ?> getAll() {
SharedPreferences sp = context.getSharedPreferences(SEARCH_HISTORY,
Context.MODE_PRIVATE);
return sp.getAll();
}
這個(gè)方法就是從SharedPreferences取出所有的值并且按照時(shí)間先后進(jìn)行排序陆淀。
@Override
public void save(String value) {
Map<String, String> historys = (Map<String, String>) getAll();
for (Map.Entry<String, String> entry : historys.entrySet()) {
if (value.equals(entry.getValue())) {
remove(entry.getKey());
}
}
put(generateKey(), value);
}
保存的方法,需要先判斷是否已經(jīng)存在先嬉,存在的話就先刪除然后根據(jù)最新的時(shí)間保存轧苫。剩下兩個(gè)移除和清空的方法就自己看吧。
@Override
public void remove(String key) {
SharedPreferences sp = context.getSharedPreferences(SEARCH_HISTORY,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove(key);
editor.commit();
}
@Override
public void clear() {
SharedPreferences sp = context.getSharedPreferences(SEARCH_HISTORY,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.commit();
}
好了疫蔓,通過(guò)sharedPreferences存儲(chǔ)歷史記錄的功能就這樣完成了含懊,但是心里一想好久沒(méi)有寫數(shù)據(jù)庫(kù)相關(guān)的代碼了,就簡(jiǎn)單的擼了一個(gè)通過(guò)數(shù)據(jù)庫(kù)存儲(chǔ)的方法衅胀,篇幅有限代碼就不貼了岔乔,想看的點(diǎn)這里。其實(shí)這里的代碼借鑒了remusic的搜索記錄存儲(chǔ)的實(shí)現(xiàn)滚躯,有什么問(wèn)題找他去→_→雏门。
界面的實(shí)現(xiàn)
界面的實(shí)現(xiàn)就比較樸素了,畢竟我們都是比較注重內(nèi)在的人掸掏,代碼就不貼了茁影,上面一個(gè)EditText,下面一個(gè)ListView再帶上一個(gè)清空的按鈕。界面寫好了之后呢丧凤,先給ListView擼個(gè)adapter吧募闲,也很簡(jiǎn)單繼承BaseAdapter,實(shí)現(xiàn)下方法,然后暴露個(gè)接口出來(lái)愿待,用于單條歷史記錄被點(diǎn)擊和刪除的時(shí)候用浩螺。
public interface OnSearchHistoryListener {
void onDelete(String key);
void onSelect(String content);
}
holder.layoutClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onSearchHistoryListener != null) {
onSearchHistoryListener.onDelete(mHistories.get(position).getTime());
}
}
});
holder.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onSearchHistoryListener != null) {
onSearchHistoryListener.onSelect(mHistories.get(position).getContent());
}
}
});
數(shù)據(jù)綁定
界面擼完了,接著就需要將數(shù)據(jù)和界面聯(lián)系起來(lái)了呼盆,這里就不得不再推薦一下MVP模式了年扩,Model和View都由中間層Presenter來(lái)控制,使得邏輯看起來(lái)變得很清晰访圃,所以這里就用MVP模式寫了厨幻,其實(shí)我是怕自己把各位繞糊涂。
public interface SearchPresenter {
void remove(String key);
void clear();
void sortHistory();
void search(String value);
}
public interface SearchView {
void showHistories(ArrayList<SearchHistoryModel> results);
void searchSuccess(String value);
}
public interface SearchModel {
void save(String value);
void search(String value,OnSearchListener onSearchListener);
void remove(String key);
void clear();
void sortHistory(OnSearchListener onSearchListener);
}
presenter要做的事情就是腿时,當(dāng)View被操作后况脆,通知它去做數(shù)據(jù)操作,即增刪改查批糟,而Model只需要在presenter告訴它做什么的時(shí)候去做就行格了,成功之后再回調(diào)presenter去通知View,這里View就只需要兩個(gè)操作,搜索成功后界面的切換以及歷史記錄的顯示徽鼎。那接下來(lái)就來(lái)實(shí)現(xiàn)一下model,presenter和View盛末。
public class SearchPresenterImpl implements SearchPresenter, OnSearchListener {
private static final int historyMax = 10;
private SearchView searchView;
private SearchModel searchModel;
public SearchPresenterImpl(SearchView searchView, Context context) {
this.searchView = searchView;
this.searchModel = new SearchModelImpl(context, historyMax);
}
//移除歷史記錄
@Override
public void remove(String key) {
searchModel.remove(key);
searchModel.sortHistory(this);
}
@Override
public void clear() {
searchModel.clear();
searchModel.sortHistory(this);
}
//獲取所有的歷史記錄
@Override
public void sortHistory() {
searchModel.sortHistory(this);
}
@Override
public void search(String value) {
searchModel.save(value);
searchModel.search(value, this);
}
@Override
public void onSortSuccess(ArrayList<SearchHistoryModel> results) {
searchView.showHistories(results);
}
@Override
public void searchSuccess(String value) {
searchView.searchSuccess(value);
}
}
在初始化presenter的同時(shí)引用了View和Model,然后實(shí)現(xiàn)OnSearchListener當(dāng)model完成操作是回調(diào)view中的方法弹惦。代碼自己看吧,應(yīng)該沒(méi)有任何疑問(wèn)悄但。
public class SearchModelImpl implements SearchModel {
private BaseHistoryStorage historyStorage;
public SearchModelImpl(Context context, int historyMax) {
// historyStorage = SpHistoryStorage.getInstance(context, historyMax);
historyStorage = DbHistoryStorage.getInstance(context, historyMax);
}
@Override
public void save(String value) {
historyStorage.save(value);
}
@Override
public void search(String value, OnSearchListener onSearchListener) {
onSearchListener.searchSuccess(value);
}
@Override
public void remove(String key) {
historyStorage.remove(key);
}
@Override
public void clear() {
historyStorage.clear();
}
@Override
public void sortHistory(OnSearchListener onSearchListener) {
onSearchListener.onSortSuccess(historyStorage.sortHistory());
}
}
model中的內(nèi)容就簡(jiǎn)單了棠隐,創(chuàng)建一個(gè)前面實(shí)現(xiàn)的BaseStorage對(duì)象,對(duì)數(shù)據(jù)進(jìn)行操作檐嚣,這里沒(méi)有對(duì)搜索做什么處理直接通過(guò)回調(diào)返回了傳進(jìn)來(lái)的字符串助泽,在實(shí)際開發(fā)中應(yīng)該是去請(qǐng)求接口,返回參數(shù)嚎京,所以在View中也沒(méi)有做具體的處理嗡贺,實(shí)際開發(fā)中可以打開一個(gè)新的頁(yè)面后者,切換列表顯示搜索到的內(nèi)容鞍帝。
@Override
public void showHistories(ArrayList<SearchHistoryModel> results) {
llSearchEmpty.setVisibility(0 != results.size() ? View.VISIBLE : View.GONE);
searchHistoryAdapter.refreshData(results);
}
@Override
public void searchSuccess(String value) {
Toast.makeText(this, value, Toast.LENGTH_SHORT).show();
}
上面就是View中實(shí)現(xiàn)的方法诫睬,獲得歷史記錄是,告訴adapter去刷新列表就行了膜眠。接下來(lái)就只剩下View中一些簡(jiǎn)單的點(diǎn)擊事件的處理了岩臣,搜索的時(shí)候調(diào)用mSearchPresenter.search(value);
,清空的時(shí)候調(diào)用mSearchPresenter.clear();
是不是感覺so easy,媽媽再也不用擔(dān)心我的學(xué)習(xí)了宵膨,當(dāng)然別忘了presenter需要在activity的onCreate方法中進(jìn)行實(shí)例化架谎。
最后呢,再給大家介紹幾個(gè)技巧:
- 不要忘記把搜索框EditText設(shè)置成Search模式
android:imeOptions="actionSearch"
設(shè)置完以后不要忘記對(duì)鍵盤上的搜索按鈕的監(jiān)聽
etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
search(etSearch.getText().toString());
return true;
}
return false;
}
});
- 我們看到很多軟件都會(huì)做模糊搜索的操作辟躏,你一輸入列表就會(huì)彈出很多相關(guān)的詞匯供你點(diǎn)擊谷扣,頓時(shí)感覺好貼心哦,其實(shí)這個(gè)功能要實(shí)現(xiàn)也不能捎琐,通過(guò)給editText設(shè)置監(jiān)聽以及Handler延時(shí)發(fā)送消息就能夠?qū)崿F(xiàn)了会涎。
private TextWatcher textWatcher = 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) {
//在500毫秒內(nèi)改變時(shí)不發(fā)送
if (mHandler.hasMessages(MSG_SEARCH)) {
mHandler.removeMessages(MSG_SEARCH);
}
if (TextUtils.isEmpty(s)) {
llSearchHistory.setVisibility(View.VISIBLE);
mSearchPresenter.sortHistory();
} else {
llSearchHistory.setVisibility(View.GONE);
//否則延遲500ms開始模糊搜索
Message message = mHandler.obtainMessage();
message.obj = s;
message.what = MSG_SEARCH;
mHandler.sendMessageDelayed(message, 500); //自動(dòng)搜索功能 刪除
}
} };//模糊搜索 private static final int MSG_SEARCH = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
search(etSearch.getText().toString().trim());
}
};
啰嗦了這么半天瑞凑,最后還是附上代碼吧末秃,searchBar
有什么Bug和可以優(yōu)化的地方還是希望大家能夠留言,你們都將是菜雞成長(zhǎng)路上的恩師籽御。
最后附上菜雞的主頁(yè)练慕,記得來(lái)逛逛哦。