Android開發(fā)自定義搜索框?qū)崿F(xiàn)源碼詳解

今天我?guī)?lái)了一個(gè)非常實(shí)用的自定義搜索框,包括搜索框、熱門搜索列表、最近常用的搜索列表等功能也差不多,可以直接重用,會(huì)大大節(jié)省你的開發(fā)時(shí)間有一點(diǎn)要負(fù)責(zé)任的告訴你,這個(gè)的實(shí)現(xiàn)是一個(gè)非常簡(jiǎn)單的自定義組合視圖除了介紹之外,我還會(huì)和大家分享具體的實(shí)現(xiàn)過(guò)程。

一,實(shí)現(xiàn)效果

效果很常見,就是平常需求中的效果,上面是搜索框,下面是最近和熱門搜索列表,為了方便大家在實(shí)際需求中使用,配置了很多屬性,也進(jìn)行了上下控件的拆分,也就是上邊搜索框和下面的搜索列表的拆分,可以按需進(jìn)行使用。


image.png

二、快速使用及屬性介紹

快速使用

目前已經(jīng)發(fā)布至遠(yuǎn)程Maven,大家可以進(jìn)行遠(yuǎn)程依賴使用。

1、在你的根項(xiàng)目下的build.gradle文件下,引入maven。

allprojects {
    repositories {
        maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
    }
}

2、在你需要使用的Module中build.gradle文件下,引入依賴。

dependencies {
    implementation 'com.vip:search:1.0.0'
}

具體代碼

1、xml中引入SearchLayout(搜索框)和SearchList(搜索列表),在實(shí)際開發(fā)中,根據(jù)需求可選擇使用,二者是互不關(guān)聯(lián)的。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="10dp"
  android:paddingRight="10dp"
  tools:context=".MainActivity">
  <com.vip.search.SearchLayout
    android:id="@+id/search_layout"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:layout_marginTop="10dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:search_bg="@drawable/shape_stroke_10" />
  <com.vip.search.SearchList
    android:id="@+id/search_list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    app:is_hot_flex_box_or_grid="true"
    app:is_visibility_history_clear="true"
    app:layout_constraintTop_toBottomOf="@id/search_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

2、代碼邏輯,以下是測(cè)試代碼,如用到實(shí)際項(xiàng)目,請(qǐng)以實(shí)際項(xiàng)目獲取控件為主。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val searchLayout = findViewById<SearchLayout>(R.id.search_layout)
        val searchList = findViewById<SearchList>(R.id.search_list)
        searchLayout.setOnTextSearchListener({
            //搜索內(nèi)容改變
        }, {
            //軟鍵盤點(diǎn)擊了搜索
            searchList.doSearchContent(it)
        })
        //設(shè)置用于測(cè)試的熱門搜索列表
        searchList.setHotList(getHotList())
        //熱門搜索條目點(diǎn)擊事件
        searchList.setOnHotItemClickListener { s, i ->
            Toast.makeText(this, s, Toast.LENGTH_SHORT).show()
        }
        //歷史搜索條目點(diǎn)擊事件
        searchList.setOnHistoryItemClickListener { s, i ->
            Toast.makeText(this, s, Toast.LENGTH_SHORT).show()
        }
    }
    /**
* AUTHOR:AbnerMing
* INTRODUCE:模擬熱門搜索列表
*/
    private val mTestHotList = arrayListOf(
        "二流小碼農(nóng)", "三流小可愛", "Android",
        "Kotlin", "iOS", "Java", "Python", "Php是世界上最好的語(yǔ)言"
    )
    private fun getHotList(): ArrayList<SearchBean> {
        return ArrayList<SearchBean>().apply {
            mTestHotList.forEachIndexed { index, s ->
                val bean = SearchBean()
                bean.content = s
                bean.isShowLeftIcon = true
                val drawable: Drawable? = if (index < 2) {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_select)
                } else if (index == 2) {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_ordinary)
                } else {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_normal)
                }
                drawable?.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
                bean.leftIcon = drawable
                add(bean)
            }
        }
    }
}

主要方法介紹

1、搜索框監(jiān)聽

拿到searchLayout控件之后,調(diào)用setOnTextSearchListener方法即可,第一個(gè)方法是搜索內(nèi)容發(fā)生變化會(huì)回調(diào),第二個(gè)方法是,點(diǎn)擊了軟鍵盤的搜索按鈕會(huì)回調(diào),如果要在最近搜索里展示,直接調(diào)用doSearchContent方法即可。

searchLayout.setOnTextSearchListener({
            //搜索內(nèi)容改變
        }, {
            //軟鍵盤點(diǎn)擊了搜索
            searchList.doSearchContent(it)
})

2、搜索列表點(diǎn)擊事件

熱門搜索調(diào)用setOnHotItemClickListener方法,歷史搜索也就是最近搜索調(diào)用setOnHistoryItemClickListener方法,都是兩個(gè)參數(shù),第一個(gè)是文本內(nèi)容,第二個(gè)是索引,也就是點(diǎn)的是哪一個(gè)。

//熱門搜索條目點(diǎn)擊事件
searchList.setOnHotItemClickListener { s, i ->
    Toast.makeText(this, s, Toast.LENGTH_SHORT).show()
}
//歷史搜索條目點(diǎn)擊事件
searchList.setOnHistoryItemClickListener { s, i ->
    Toast.makeText(this, s, Toast.LENGTH_SHORT).show()
}

3、改變最近(歷史)搜索item背景

有的老鐵說(shuō)了,默認(rèn)的背景我不喜歡,能否可以動(dòng)態(tài)設(shè)置,必須能!


image.png

設(shè)置背景,通過(guò)setHistoryItemBg方法。

searchList.setHistoryItemBg(R.drawable.shape_solid_d43c3c_10)

效果展示

image.png

4、動(dòng)態(tài)設(shè)置熱門搜索熱度
可能在很多需求中,需要展示幾個(gè)熱度,有的是按照顏色區(qū)分,如下圖:

image.png

實(shí)現(xiàn)起來(lái)很簡(jiǎn)單,在設(shè)置熱門列表(setHotList)的時(shí)候,針對(duì)傳遞的對(duì)象設(shè)置leftIcon即可。測(cè)試代碼如下:

private fun getHotList(): ArrayList<SearchBean> {
        return ArrayList<SearchBean>().apply {
            mTestHotList.forEachIndexed { index, s ->
                val bean = SearchBean()
                bean.content = s
                bean.isShowLeftIcon = true
                val drawable: Drawable? = if (index < 2) {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_select)
                } else if (index == 2) {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_ordinary)
                } else {
                    ContextCompat.getDrawable(this@MainActivity, R.drawable.shape_circle_normal)
                }
                drawable?.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
                bean.leftIcon = drawable
                add(bean)
            }
        }
    }

具體的哪個(gè)數(shù)據(jù)展示什么顏色,直接設(shè)置即可,想怎么展示就怎么展示。當(dāng)然了除了展示不同的熱度之外,還有一些其他的變量,isShowLeftIcon為是否展示文字左邊的icon,textColor為當(dāng)前文字的顏色,根據(jù)不同的顏色,我們也可以實(shí)現(xiàn)下面的效果。


image.png

屬性介紹

為了讓功能靈活多變,也為了滿足更多的需求樣式,目前自定義了很多屬性,大家可以按自己的需要進(jìn)行設(shè)置,或者直接去GitHub中下載源碼更改也可以。

SearchLayout(搜索框?qū)傩裕?/strong>

image.png

SearchList(搜索列表屬性)

image.png

三、具體代碼實(shí)現(xiàn)

關(guān)于這個(gè)組合View的實(shí)現(xiàn)方式,我是分為了兩個(gè)View,大家在上邊的使用中應(yīng)該也看到了,一個(gè)是搜索框SearchLayout,一個(gè)是搜索框下面的搜索列表展示SearchList,開頭就闡述了,沒(méi)啥技術(shù)含量,簡(jiǎn)單的羅列下代碼實(shí)現(xiàn)吧。

SearchLayout是一個(gè)組合View,中間是一個(gè)EditText,左右兩邊是一個(gè)ImageView,也就是搜索圖標(biāo)和刪除圖標(biāo),如下圖:

image.png

SearchLayout本身沒(méi)有啥要說(shuō)的,無(wú)非就是把View組合到了一起,在開發(fā)的時(shí)候,既然要給別人使用,那么就要拓展出很多的動(dòng)態(tài)屬性或者方法出來(lái),這是很重要的,所以,在封裝的時(shí)候,自定義屬性無(wú)比的重要,需要精確和認(rèn)真,這一塊沒(méi)啥好說(shuō)的,有一點(diǎn)需要注意,也就是EditText綁定軟鍵盤搜索,除了設(shè)置屬性android:imeOptions="actionSearch",也要設(shè)置,android:singleLine="true",方可生效。

SearchList其實(shí)也沒(méi)啥好說(shuō)的,也是一個(gè)組合View,使用的是上下兩個(gè)RecyclerView來(lái)實(shí)現(xiàn)的,至于流失布局,采用的是google提供的flexbox,設(shè)置布局管理器即可。

recyclerView.layoutManager = FlexboxLayoutManager(mContext)

除了這個(gè)之外,可能需要闡述的也就是最近搜索的存儲(chǔ)機(jī)制了,存儲(chǔ)呢,Android中提供了很多的存儲(chǔ)方式,比如數(shù)據(jù)庫(kù),SharedPreferences,SD卡,還有DataStore,MMKV等,無(wú)論哪一種吧,選擇適合的即可,這個(gè)開源中,不想引入其他的三方了,直接使用的是SharedPreferences。

具體的實(shí)現(xiàn)方式,把搜索的內(nèi)容,轉(zhuǎn)成json串,以json串的形式進(jìn)行存儲(chǔ),這里借助了原生的JSONArray和JSONObject。流程就是,觸發(fā)搜索內(nèi)容后,先從SharedPreferences取出之前存儲(chǔ)的內(nèi)容,放到JSONArray中,當(dāng)前搜索內(nèi)容如果存在JSONArray中,那邊就要執(zhí)行刪除原來(lái)的,再把新的內(nèi)容插入到第一個(gè)的位置,如果不存在JSONArray中,直接添加即可,隨后再轉(zhuǎn)成字符串存儲(chǔ)即可。

當(dāng)然了,一般在正常的需求開發(fā)中,最近搜索列表肯定不是無(wú)限展示的,都有固定的展示個(gè)數(shù),比如10個(gè),比如15個(gè),所以,當(dāng)超過(guò)指定的個(gè)數(shù),也就是指定的閥門后,就要執(zhí)行刪除的操作。

val searchHistory = getSearchHistory()
if (!TextUtils.isEmpty(it)) {
    val jsonArray: JSONArray = if (TextUtils.isEmpty(searchHistory)) {
        JSONArray()
    } else {
        JSONArray(searchHistory)
    }
    val json = JSONObject()
    json.put("content", it)
    //如果出現(xiàn)了一樣的,刪除后,加到第一個(gè)
    var isEqual = false
    var equalPosition = 0
    for (i in 0 until jsonArray.length()) {
        val item = jsonArray.getJSONObject(i)
        val content = item.getString("content")
        if (it == content) {
            isEqual = true
            equalPosition = i
            break
        }
    }
    //有一樣的
    if (isEqual) {
        jsonArray.remove(equalPosition)
    } else {
        //超過(guò)了指定的閥門之后,就不在擴(kuò)充
        if (jsonArray.length() >= mHistoryListSize) {
            jsonArray.remove(0)
        }
    }
    jsonArray.put(json)
    SearchSharedPreUtils.put(mContext!!, "search_history", jsonArray.toString())
}
getSearchHistory()?.let {
    eachSearchHistory(it)
}
//兩個(gè)有一個(gè)不為空,展示
if (!TextUtils.isEmpty(it) || !TextUtils.isEmpty(searchHistory)) {
    showOrHideHistoryLayout(View.VISIBLE)

當(dāng)然了,存儲(chǔ)的邏輯,有很多的實(shí)現(xiàn)的方式,這里并不是最優(yōu)的,只是提供了一種思路,大家可以按照自己的方式來(lái)操作。

搜索列表,無(wú)論是熱門還是最近的搜索列表,均支持網(wǎng)格和流失布局形式展示,大家看屬性相關(guān)介紹中即可。這個(gè)搜索框本身就是很簡(jiǎn)單的效果還有代碼,大家直接看源碼或文中介紹即可,就不多贅述了!希望這篇文章內(nèi)容能給大家提供幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容