Mengonversi EditText menjadi SearchEditText

gambar



Pernahkah Anda mencoba menyesuaikan tampilan atau perilaku komponen SearchView standar? Saya rasa begitu. Dalam kasus ini, saya pikir Anda akan setuju bahwa tidak semua pengaturannya cukup fleksibel untuk memenuhi semua persyaratan bisnis dari tugas tertentu. Salah satu cara untuk mengatasi masalah ini adalah dengan menulis SearchView "kustom" Anda sendiri, yang akan kita lakukan hari ini. Pergilah!



Catatan: tampilan yang dibuat (selanjutnya - SearchEditText ) tidak akan memiliki semua properti SearchView standar. Jika perlu, Anda dapat dengan mudah menambahkan opsi tambahan untuk kebutuhan khusus.



Rencana aksi



Ada beberapa hal yang perlu kita lakukan untuk "mengubah" EditText menjadi SearchEditText. Singkatnya, kami membutuhkan:



  • Mewarisi SearchEditText dari AppCompatEditText
  • Tambahkan ikon "Cari" di pojok kiri (atau kanan) SearchEditText, saat mengklik kueri penelusuran yang dimasukkan akan dikirim ke pendengar terdaftar
  • Tambahkan ikon "Pembersihan" di sudut kanan (atau kiri) SearchEditText, saat Anda mengekliknya, teks yang dimasukkan di bilah pencarian akan dihapus
  • Setel parameter SearchEditText imeOptions ke IME_ACTION_SEARCH, sehingga saat keyboard muncul, tombol masukan teks berfungsi sebagai tombol "Telusuri"


SearchEditText dengan segala kemuliaannya!



import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View.OnTouchListener
import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.widget.doAfterTextChanged

class SearchEditText
@JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyle: Int = androidx.appcompat.R.attr.editTextStyle
) : AppCompatEditText(context, attributeSet, defStyle) {

    init {
        setLeftDrawable(android.R.drawable.ic_menu_search)
        setTextChangeListener()
        setOnEditorActionListener()
        setDrawablesListener()
        imeOptions = EditorInfo.IME_ACTION_SEARCH
    }

    companion object {
        private const val DRAWABLE_LEFT_INDEX = 0
        private const val DRAWABLE_RIGHT_INDEX = 2
    }

    private var queryTextListener: QueryTextListener? = null

    private fun setTextChangeListener() {
        doAfterTextChanged {
            if (it.isNullOrBlank()) {
                setRightDrawable(0)
            } else {
                setRightDrawable(android.R.drawable.ic_menu_close_clear_cancel)
            }
            queryTextListener?.onQueryTextChange(it.toString())
        }
    }
    
    private fun setOnEditorActionListener() {
        setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                queryTextListener?.onQueryTextSubmit(text.toString())
                true
            } else {
                false
            }
        }
    }
    
    private fun setDrawablesListener() {
        setOnTouchListener(OnTouchListener { view, event ->
            view.performClick()
            if (event.action == MotionEvent.ACTION_UP) {
                when {
                    rightDrawableClicked(event) -> {
                        setText("")
                        return@OnTouchListener true
                    }
                    leftDrawableClicked(event) -> {
                        queryTextListener?.onQueryTextSubmit(text.toString())
                        return@OnTouchListener true
                    }
                    else -> {
                        return@OnTouchListener false
                    }
                }
            }
            false
        })
    }

    private fun rightDrawableClicked(event: MotionEvent): Boolean {

        val rightDrawable = compoundDrawables[DRAWABLE_RIGHT_INDEX]

        return if (rightDrawable == null) {
            false
        } else {
            val startOfDrawable = width - rightDrawable.bounds.width() - paddingRight
            val endOfDrawable = startOfDrawable + rightDrawable.bounds.width()
            startOfDrawable <= event.x && event.x <= endOfDrawable
        }

    }

    private fun leftDrawableClicked(event: MotionEvent): Boolean {

        val leftDrawable = compoundDrawables[DRAWABLE_LEFT_INDEX]

        return if (leftDrawable == null) {
            false
        } else {
            val startOfDrawable = paddingLeft
            val endOfDrawable = startOfDrawable + leftDrawable.bounds.width()
            startOfDrawable <= event.x && event.x <= endOfDrawable
        }

    }

    fun setQueryTextChangeListener(queryTextListener: QueryTextListener) {
        this.queryTextListener = queryTextListener
    }

    interface QueryTextListener {
        fun onQueryTextSubmit(query: String?)
        fun onQueryTextChange(newText: String?)
    }

}


Pada kode di atas, dua fungsi ekstensi digunakan untuk mengatur gambar kanan dan kiri EditText. Kedua fungsi tersebut terlihat seperti ini:



import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat

private const val DRAWABLE_LEFT_INDEX = 0
private const val DRAWABLE_TOP_INDEX = 1
private const val DRAWABLE_RIGHT_INDEX = 2
private const val DRAWABLE_BOTTOM_INDEX = 3

fun TextView.setLeftDrawable(@DrawableRes drawableResId: Int) {

    val leftDrawable = if (drawableResId != 0) {
        ContextCompat.getDrawable(context, drawableResId)
    } else {
        null
    }
    val topDrawable = compoundDrawables[DRAWABLE_TOP_INDEX]
    val rightDrawable = compoundDrawables[DRAWABLE_RIGHT_INDEX]
    val bottomDrawable = compoundDrawables[DRAWABLE_BOTTOM_INDEX]

    setCompoundDrawablesWithIntrinsicBounds(
        leftDrawable,
        topDrawable,
        rightDrawable,
        bottomDrawable
    )

}

fun TextView.setRightDrawable(@DrawableRes drawableResId: Int) {

    val leftDrawable = compoundDrawables[DRAWABLE_LEFT_INDEX]
    val topDrawable = compoundDrawables[DRAWABLE_TOP_INDEX]
    val rightDrawable = if (drawableResId != 0) {
        ContextCompat.getDrawable(context, drawableResId)
    } else {
        null
    }
    val bottomDrawable = compoundDrawables[DRAWABLE_BOTTOM_INDEX]

    setCompoundDrawablesWithIntrinsicBounds(
        leftDrawable,
        topDrawable,
        rightDrawable,
        bottomDrawable
    )

}


Mewarisi dari AppCompatEditText



class SearchEditText
@JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyle: Int = androidx.appcompat.R.attr.editTextStyle
) : AppCompatEditText(context, attributeSet, defStyle)


Seperti yang Anda lihat, dari konstruktor tertulis, kami meneruskan semua parameter yang diperlukan ke konstruktor AppCompatEditText. Poin penting di sini adalah defStyle defaultnya adalah android.appcompat.R.attr.editTextStyle. Mewarisi dari LinearLayout, FrameLayout dan beberapa tampilan lainnya, kami cenderung menggunakan 0 sebagai default untuk defStyle. Namun, dalam kasus kami, ini tidak cocok, jika tidak SearchEditText kami akan berperilaku seperti TextView, dan tidak seperti EditText.



Memproses perubahan teks



Hal berikutnya yang perlu kita lakukan adalah "mempelajari" bagaimana menanggapi peristiwa perubahan teks di SearchEditText kita. Kami membutuhkan ini karena dua alasan:



  • menampilkan atau menyembunyikan ikon hapus tergantung pada apakah teks dimasukkan
  • memberi tahu pendengar untuk mengubah teks di SearchEditText


Mari kita lihat kode pendengar:



private fun setTextChangeListener() {
    doAfterTextChanged {
        if (it.isNullOrBlank()) {
            setRightDrawable(0)
        } else {
            setRightDrawable(android.R.drawable.ic_menu_close_clear_cancel)
        }
        queryTextListener?.onQueryTextChange(it.toString())
    }
}


Untuk menangani peristiwa perubahan teks, fungsi ekstensi doAfterTextChanged dari androidx.core: core-ktx digunakan.



Tangani klik tombol enter pada keyboard



Saat pengguna menekan tombol enter pada keyboard, ia memeriksa untuk melihat apakah aksinya adalah IME_ACTION_SEARCH. Jika demikian, kami memberi tahu pendengar tentang tindakan ini dan meneruskan teks dari SearchEditText ke sana. Mari kita lihat bagaimana ini terjadi.



private fun setOnEditorActionListener() {
    setOnEditorActionListener { _, actionId, _ ->
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            queryTextListener?.onQueryTextSubmit(text.toString())
            true
        } else {
            false
        }
    }
}


Menangani klik ikon



Dan terakhir, pertanyaan terakhir, namun tidak kalah pentingnya, - bagaimana menangani klik pada ikon pencarian dan teks yang jelas. Tangkapannya di sini adalah, secara default, drawable dari EditText standar tidak merespons peristiwa klik, yang berarti tidak ada listener resmi yang bisa menanganinya.



Untuk mengatasi masalah ini, OnTouchListener telah terdaftar di SearchEditText. Saat disentuh, menggunakan fungsi leftDrawableClicked dan rightDrawableClicked, sekarang kita dapat menangani klik pada ikon. Mari kita lihat kodenya:



private fun setDrawablesListener() {
    setOnTouchListener(OnTouchListener { view, event ->
        view.performClick()
        if (event.action == MotionEvent.ACTION_UP) {
            when {
                rightDrawableClicked(event) -> {
                    setText("")
                    return@OnTouchListener true
                }
                leftDrawableClicked(event) -> {
                    queryTextListener?.onQueryTextSubmit(text.toString())
                    return@OnTouchListener true
                }
                else -> {
                    return@OnTouchListener false
                }
            }
        }
        false
    })
}

private fun rightDrawableClicked(event: MotionEvent): Boolean {

    val rightDrawable = compoundDrawables[DRAWABLE_RIGHT_INDEX]

    return if (rightDrawable == null) {
        false
    } else {
        val startOfDrawable = width - rightDrawable.bounds.width() - paddingRight
        val endOfDrawable = startOfDrawable + rightDrawable.bounds.width()
        startOfDrawable <= event.x && event.x <= endOfDrawable
    }

}

private fun leftDrawableClicked(event: MotionEvent): Boolean {

    val leftDrawable = compoundDrawables[DRAWABLE_LEFT_INDEX]

    return if (leftDrawable == null) {
        false
    } else {
        val startOfDrawable = paddingLeft
        val endOfDrawable = startOfDrawable + leftDrawable.bounds.width()
        startOfDrawable <= event.x && event.x <= endOfDrawable
    }

}


Tidak ada yang rumit tentang fungsi leftDrawableClicked dan RightDrawableClicked. Ambil yang pertama, misalnya. Untuk ikon kiri, pertama kita menghitung startOfDrawable dan endOfDrawable lalu memeriksa apakah koordinat x dari titik sentuh berada dalam rentang [startofDrawable, endOfDrawable]. Jika ya, berarti ikon kiri ditekan. Fungsi rightDrawableClicked bekerja dengan cara yang serupa.



Bergantung pada apakah ikon kiri atau kanan ditekan, kami melakukan tindakan tertentu. Saat kami mengklik ikon kiri (ikon pencarian), kami memberi tahu pendengar tentang hal ini dengan memanggil fungsi onQueryTextSubmit. Saat Anda mengklik yang benar, kami menghapus teks SearchEditText.



Keluaran



Pada artikel ini, kami melihat pilihan untuk "mengubah" EditText standar menjadi SearchEditText yang lebih canggih. Seperti yang disebutkan sebelumnya, solusi out-of-the-box tidak mendukung semua opsi yang disediakan oleh SearchView, namun, Anda dapat memperbaikinya kapan saja dengan menambahkan opsi tambahan sesuai kebijaksanaan Anda. Lakukan!



PS:

Anda dapat mengakses kode sumber untuk SearchEditText dari repositori GitHub ini .



All Articles