Konvertieren von EditText in SearchEditText

Bild



Haben Sie jemals versucht, das Erscheinungsbild oder Verhalten der Standard-SearchView-Komponente anzupassen? Ich denke schon. In diesem Fall werden Sie meiner Meinung nach zustimmen, dass nicht alle Einstellungen flexibel genug sind, um alle Geschäftsanforderungen einer einzelnen Aufgabe zu erfüllen. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine eigene "benutzerdefinierte" Suchansicht zu schreiben, die wir heute ausführen werden. Gehen!



Hinweis: Die erstellte Ansicht (im Folgenden - SearchEditText ) verfügt nicht über alle Eigenschaften der Standard-Suchansicht. Bei Bedarf können Sie problemlos zusätzliche Optionen für bestimmte Anforderungen hinzufügen.



Aktionsplan



Es gibt verschiedene Dinge, die wir tun müssen, um einen EditText in einen SearchEditText zu "verwandeln". Kurz gesagt, wir brauchen:



  • Erben Sie SearchEditText von AppCompatEditText
  • Fügen Sie in der linken (oder rechten) Ecke von SearchEditText ein "Such" -Symbol hinzu, wenn Sie darauf klicken, dass die eingegebene Suchabfrage an den registrierten Listener übertragen wird
  • Fügen Sie in der rechten (oder linken) Ecke von SearchEditText ein "Bereinigungs" -Symbol hinzu. Wenn Sie darauf klicken, wird der in der Suchleiste eingegebene Text gelöscht
  • Setzen Sie den Parameter imeOptions SearchEditText auf IME_ACTION_SEARCH, sodass die Texteingabeschaltfläche beim Anzeigen der Tastatur als Schaltfläche "Suchen" fungiert


SearchEditText in seiner ganzen Pracht!



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?)
    }

}


Im obigen Code wurden zwei Erweiterungsfunktionen verwendet, um das rechte und linke Bild des EditText festzulegen. Diese beiden Funktionen sehen folgendermaßen aus:



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
    )

}


Erben von AppCompatEditText



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


Wie Sie sehen können, übergeben wir vom geschriebenen Konstruktor alle erforderlichen Parameter an den AppCompatEditText-Konstruktor. Der wichtige Punkt hierbei ist, dass der Standard-DefStyle android.appcompat.R.attr.editTextStyle ist. Beim Erben von LinearLayout, FrameLayout und einigen anderen Ansichten wird in der Regel 0 als Standard für defStyle verwendet. In unserem Fall ist dies jedoch nicht geeignet, da sich unser SearchEditText sonst wie eine Textansicht und nicht wie ein EditText verhält.



Textänderungen verarbeiten



Als nächstes müssen wir lernen, wie wir auf Textänderungsereignisse in unserem SearchEditText reagieren. Wir brauchen dies aus zwei Gründen:



  • Ein- oder Ausblenden des Löschsymbols, je nachdem, ob der Text eingegeben wurde
  • Benachrichtigen des Listeners, um den Text in SearchEditText zu ändern


Schauen wir uns den Listener-Code an:



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


Zur Behandlung von Textänderungsereignissen wurde die Erweiterungsfunktion doAfterTextChanged von androidx.core: core-ktx verwendet.



Behandeln Sie das Klicken der Eingabetaste auf der Tastatur



Wenn der Benutzer die Eingabetaste auf der Tastatur drückt, prüft er, ob die Aktion IME_ACTION_SEARCH lautet. Wenn ja, informieren wir den Listener über diese Aktion und übergeben den Text von SearchEditText an ihn. Mal sehen, wie das passiert.



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


Umgang mit Symbolklicks



Und schließlich die letzte Frage, wie man mit dem Klicken auf Suchsymbole und Klartext umgeht. Der Haken dabei ist, dass die Drawables des Standard-EditText standardmäßig nicht auf Klickereignisse reagieren, was bedeutet, dass es keinen offiziellen Listener gibt, der damit umgehen kann.



Um dieses Problem zu lösen, wurde ein OnTouchListener im SearchEditText registriert. Bei Berührung können wir mit den Funktionen leftDrawableClicked und rightDrawableClicked jetzt auf Symbole klicken. Werfen wir einen Blick auf den Code:



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
    }

}


Die Funktionen leftDrawableClicked und RightDrawableClicked sind nicht kompliziert. Nehmen Sie zum Beispiel den ersten. Für das linke Symbol berechnen wir zuerst startOfDrawable und endOfDrawable und prüfen dann, ob die x-Koordinate des Berührungspunkts im Bereich [startofDrawable, endOfDrawable] liegt. Wenn ja, bedeutet dies, dass das linke Symbol gedrückt wurde. Die Funktion rightDrawableClicked funktioniert auf ähnliche Weise.



Je nachdem, ob das linke oder das rechte Symbol gedrückt wird, führen wir bestimmte Aktionen aus. Wenn wir auf das linke Symbol (Suchsymbol) klicken, informieren wir den Listener darüber, indem wir seine Funktion onQueryTextSubmit aufrufen. Wenn Sie auf das richtige klicken, wird der SearchEditText-Text gelöscht.



Ausgabe



In diesem Artikel haben wir uns die Option angesehen, einen Standard-EditText in einen erweiterten SearchEditText umzuwandeln. Wie bereits erwähnt, unterstützt die Out-of-the-Box-Lösung nicht alle von SearchView bereitgestellten Optionen. Sie können sie jedoch jederzeit verbessern, indem Sie nach eigenem Ermessen zusätzliche Optionen hinzufügen. Tue es!



PS:

Sie können über dieses GitHub-Repository auf den Quellcode für SearchEditText zugreifen .



All Articles