簡(jiǎn)單的帶歷史記錄的搜索功能實(shí)現(xiàn)

這兩天閑來(lái)無(wú)事,就把前短時(shí)間項(xiàng)目中的搜索功能抽取出來(lái)狰闪,重新寫一下疯搅,搜索功能雖然簡(jiǎn)單,但是設(shè)計(jì)到得知識(shí)點(diǎn)也挺多的埋泵,就當(dāng)做一個(gè)總結(jié)吧幔欧。
代碼地址在最后面罪治,話不多說(shuō),上效果圖 _

searchdemo.gif

歷史記錄的存儲(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è)技巧:

  1. 不要忘記把搜索框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;
            }
        });

  1. 我們看到很多軟件都會(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)逛逛哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末技掏,一起剝皮案震驚了整個(gè)濱河市铃将,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哑梳,老刑警劉巖劲阎,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鸠真,居然都是意外死亡悯仙,警方通過(guò)查閱死者的電腦和手機(jī)龄毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雁比,“玉大人稚虎,你說(shuō)我怎么就攤上這事撤嫩≠松樱” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵序攘,是天一觀的道長(zhǎng)茴她。 經(jīng)常有香客問(wèn)我,道長(zhǎng)程奠,這世上最難降的妖魔是什么丈牢? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮瞄沙,結(jié)果婚禮上己沛,老公的妹妹穿的比我還像新娘。我一直安慰自己距境,他們只是感情好申尼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垫桂,像睡著了一般师幕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诬滩,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天霹粥,我揣著相機(jī)與錄音,去河邊找鬼疼鸟。 笑死后控,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的空镜。 我是一名探鬼主播浩淘,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼姑裂!你這毒婦竟也來(lái)了馋袜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舶斧,失蹤者是張志新(化名)和其女友劉穎欣鳖,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茴厉,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泽台,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年什荣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀酷。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稻爬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜕依,到底是詐尸還是另有隱情桅锄,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布样眠,位于F島的核電站友瘤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏檐束。R本人自食惡果不足惜辫秧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望被丧。 院中可真熱鬧盟戏,春花似錦、人聲如沸甥桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)格嘁。三九已至笛求,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糕簿,已是汗流浹背探入。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留懂诗,地道東北人蜂嗽。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像殃恒,于是被迫代替她去往敵國(guó)和親植旧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容