這兩天閑來無事,就把前短時間項目中的搜索功能抽取出來,重新寫一下,搜索功能雖然簡單,但是設計到得知識點也挺多的,就當做一個總結吧。
代碼地址在最后面,話不多說,上效果圖 _
歷史記錄的存儲
首先來說下關于歷史記錄的存儲,歷史記錄的存儲方式其實可以有很多方法,可以用sp,數據庫等等,那么就直接開擼吧。說開擼你還真以為就直接開擼了,還是先想想吧,我們做歷史記錄存儲的時候需要提供什么給調用者,其實很簡單無非就是可以增刪改查嗎。為了遵守里氏替換原則,就先寫了個抽象類BaseHistoryStorage,里面有幾個抽象方法,至于是什么自己看吧。這樣不管你是用SP還是數據庫甚至其他更牛的技術,只需要集成Base類,實現這些抽象方法就行了,至于你是怎么實現的,我才不管呢。
/**
* 歷史信息存儲基類
* Created by Zellerpooh on 17/1/18.
*/
public abstract class BaseHistoryStorage {
/**
* 保存歷史記錄時調用
*
* @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是我寫的一個JavaBean,里面就放了兩個String,一個是歷史搜索的內容,一個是歷史記錄的Key,其實你直接返回一個String泛型的ArrayList的就行,但是我這里為了用SP實現的時候跟快速偷了個懶,好了自己去實現一個歷史記錄存儲功能把。
通過SharedPreference實現數據存儲
聽到讓你自己去實現是不是心涼了一半,當然是逗你的了,既然都來了怎么能不給你點福利呢,下面我就實現一個簡單的通過SharedPreference實現的數據存儲吧,來拋磚迎玉吧。
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;
}
作為一個勵志成為高逼格的高級程序員的菜鳥,當然不會放過任何裝逼的機會,對于這種比較耗資源的數據存儲,將他設計為單例模式當然最合適不過了,上面就是一個簡單的DCL的單例模式實現,在安卓中將Context傳入到單例中是一個大忌,你試想一下你的activity永遠得不到釋放是一件多么恐怖的事情,所以我就換成了applicationContext,反正他是跟隨程序一直在的。
然后來看看里面的方法實現吧
private static SimpleDateFormat mFormat = new SimpleDateFormat("yyyyMMddHHmmss");
@Override
public String generateKey() {
return mFormat.format(new Date());
}
上面這個方法就是為了存儲的時候根據當前時間生成的一個Key,用來判斷先后順序。
@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;
//這里計算 如果歷史記錄條數是大于 可以顯示的最大條數,則用最大條數做循環條件,防止歷史記錄條數-最大條數為負值,數組越界
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();
}
這個方法就是從SharedPreferences取出所有的值并且按照時間先后進行排序。
@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);
}
保存的方法,需要先判斷是否已經存在,存在的話就先刪除然后根據最新的時間保存。剩下兩個移除和清空的方法就自己看吧。
@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();
}
好了,通過sharedPreferences存儲歷史記錄的功能就這樣完成了,但是心里一想好久沒有寫數據庫相關的代碼了,就簡單的擼了一個通過數據庫存儲的方法,篇幅有限代碼就不貼了,想看的點這里。其實這里的代碼借鑒了remusic的搜索記錄存儲的實現,有什么問題找他去→_→。
界面的實現
界面的實現就比較樸素了,畢竟我們都是比較注重內在的人,代碼就不貼了,上面一個EditText,下面一個ListView再帶上一個清空的按鈕。界面寫好了之后呢,先給ListView擼個adapter吧,也很簡單繼承BaseAdapter,實現下方法,然后暴露個接口出來,用于單條歷史記錄被點擊和刪除的時候用。
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());
}
}
});
數據綁定
界面擼完了,接著就需要將數據和界面聯系起來了,這里就不得不再推薦一下MVP模式了,Model和View都由中間層Presenter來控制,使得邏輯看起來變得很清晰,所以這里就用MVP模式寫了,其實我是怕自己把各位繞糊涂。
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要做的事情就是,當View被操作后,通知它去做數據操作,即增刪改查,而Model只需要在presenter告訴它做什么的時候去做就行,成功之后再回調presenter去通知View,這里View就只需要兩個操作,搜索成功后界面的切換以及歷史記錄的顯示。那接下來就來實現一下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的同時引用了View和Model,然后實現OnSearchListener當model完成操作是回調view中的方法。代碼自己看吧,應該沒有任何疑問。
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中的內容就簡單了,創建一個前面實現的BaseStorage對象,對數據進行操作,這里沒有對搜索做什么處理直接通過回調返回了傳進來的字符串,在實際開發中應該是去請求接口,返回參數,所以在View中也沒有做具體的處理,實際開發中可以打開一個新的頁面后者,切換列表顯示搜索到的內容。
@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中實現的方法,獲得歷史記錄是,告訴adapter去刷新列表就行了。接下來就只剩下View中一些簡單的點擊事件的處理了,搜索的時候調用mSearchPresenter.search(value);
,清空的時候調用mSearchPresenter.clear();
是不是感覺so easy,媽媽再也不用擔心我的學習了,當然別忘了presenter需要在activity的onCreate方法中進行實例化。
最后呢,再給大家介紹幾個技巧:
- 不要忘記把搜索框EditText設置成Search模式
android:imeOptions="actionSearch"
設置完以后不要忘記對鍵盤上的搜索按鈕的監聽
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;
}
});
- 我們看到很多軟件都會做模糊搜索的操作,你一輸入列表就會彈出很多相關的詞匯供你點擊,頓時感覺好貼心哦,其實這個功能要實現也不能,通過給editText設置監聽以及Handler延時發送消息就能夠實現了。
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毫秒內改變時不發送
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); //自動搜索功能 刪除
}
} };//模糊搜索 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和可以優化的地方還是希望大家能夠留言,你們都將是菜雞成長路上的恩師。
最后附上菜雞的主頁,記得來逛逛哦。