Русский
Русский
English
Статистика
Реклама

Listener

Превращаем EditText в SearchEditText

12.09.2020 22:20:08 | Автор: admin
image

Пробовали ли Вы когда-нибудь настроить внешний вид или поведение стандартного компонента SearchView? Полагаю, да. В этом случае, я думаю что вы согласитесь, что далеко не все его настройки являются достаточно гибкими, для того, чтобы удовлетворить всем бизнес-требованиям отдельно взятой задачи. Одним из способов решения этой проблемы является написание собственного кастомного SearchView, чем мы сегодня и займемся. Поехали!

Примечание: создаваемое view (далее SearchEditText), не будет обладать всеми свойствами стандартного SearchView. В случае необходимости, вы можете без труда добавить дополнительные опции под конкретные нужды.

План действий


Есть несколько вещей, которые нам нужно сделать, для превращения EditText в SearchEditText. Если кратко, то нам нужно:

  • Унаследовать SearchEditText от AppCompatEditText
  • Добавить иконку Поиск в левом (или правом) углу SearchEditText, при нажатии на которую введённый поисковый запрос будет передаваться зарегистрированному слушателю
  • Добавить иконку Очистка в правом (или левом) углу SearchEditText, при нажатии на которую введённый текст в поисковой строке будет очищаться
  • Установить в параметре imeOptions SearchEditText-а значение IME_ACTION_SEARCH, для того, чтобы при появлении клавиатуры кнопка ввода текста выполняла роль кнопки Поиск

SearchEditText во всей красе!


import android.content.Contextimport android.util.AttributeSetimport android.view.MotionEventimport android.view.View.OnTouchListenerimport android.view.inputmethod.EditorInfoimport androidx.appcompat.widget.AppCompatEditTextimport androidx.core.widget.doAfterTextChangedclass 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?)    }}

В приведенном выше коде были использованы две extension-функции для установки правого и левого изображения EditText-а. Эти две функции выглядят следующим образом:

import android.widget.TextViewimport androidx.annotation.DrawableResimport androidx.core.content.ContextCompatprivate const val DRAWABLE_LEFT_INDEX = 0private const val DRAWABLE_TOP_INDEX = 1private const val DRAWABLE_RIGHT_INDEX = 2private const val DRAWABLE_BOTTOM_INDEX = 3fun 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    )}

Наследование от AppCompatEditText


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

Как видите, из написанного конструктора мы передаём все необходимые параметры в конструктор AppCompatEditText. Важным моментом тут является то, что значением defStyle по-умолчанию является android.appcompat.R.attr.editTextStyle. Наследуясь от LinearLayout, FrameLayout и некоторых других view, мы, как правило, используем 0 в качестве значения по-умолчанию для defStyle. Однако в нашем случае это не подходит, иначе наш SearchEditText будет вести себя как TextView, а не как EditText.

Обработка изменения текста


Следующее, что нам нужно сделать, это научиться реагировать на события изменения текста нашего SearchEditText. Нам это необходимо по двум причинам:

  • отображение или скрытие иконки очистки в зависимости от того, введён ли текст
  • оповещение слушателя об изменении текста в SearchEditText

Посмотрим на код слушателя:

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

Для обработки событий изменения текста использовалась extension-функция doAfterTextChanged из androidx.core:core-ktx.

Обработка нажатия кнопки ввода на клавиатуре


Когда пользователь нажимает клавишу ввода на клавиатуре, происходит проверка на то, является ли это действие IME_ACTION_SEARCH. Если это так, то мы сообщаем слушателю об этом действии и передаем ему текст из SearchEditText. Посмотрим как это происходит.

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

Обработка нажатий на иконки


Ну и наконец, последний по счету, но не по значению вопрос обработка нажатия на иконки поиска и очистки текста. Здесь загвоздка заключается в том, что по-умолчанию drawables стандартного EditText-а не реагируют на события нажатия, а значит и официального слушателя, который мог бы их обрабатывать, нет.

Для решения этой проблемы был зарегистрирован OnTouchListener в SearchEditText. При касании, с помощью функций leftDrawableClicked и rightDrawableClicked мы теперь можем обрабатывать клик по иконкам. Взглянем на код:

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

В функциях leftDrawableClicked и RightDrawableClicked нет ничего сложного. Возьмём, к примеру, первую из них. Для левой иконки мы сначала рассчитываем startOfDrawable и endOfDrawable, а затем проверяем, находится ли x-координата точки касания в диапазоне [startofDrawable, endOfDrawable]. Если да, то это означает, что левая иконка была нажата. Функция rightDrawableClicked работает аналогичным образом.

В зависимости от того, нажата ли левая или правая иконка, мы осуществляем те или иные действия. При нажатии на левую иконку (значок поиска) мы сообщаем об этом слушателю, вызывая его функцию onQueryTextSubmit. При нажатии на правую очищаем текст SearchEditText.

Вывод


В этой статье мы рассмотрели вариант превращения стандартного EditText в более продвинутый SearchEditText. Как уже упоминалось ранее, готовое решение не поддерживает все параметры, предоставляемые SearchView, однако вы в любой момент можете его усовершенствовать, добавив дополнительные опции на свое усмотрение. Дерзайте!

P.S:

Доступ к исходному коду SearchEditText вы можете получить из этого репозитория GitHub.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru