[Разработка под Android] Превращаем EditText в SearchEditText
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Пробовали ли Вы когда-нибудь настроить внешний вид или поведение стандартного компонента SearchView? Полагаю, да. В этом случае, я думаю что вы согласитесь, что далеко не все его настройки являются достаточно гибкими, для того, чтобы удовлетворить всем бизнес-требованиям отдельно взятой задачи. Одним из способов решения этой проблемы является написание собственного «кастомного» SearchView, чем мы сегодня и займемся. Поехали!
Примечание: создаваемое view (далее – SearchEditText), не будет обладать всеми свойствами стандартного SearchView. В случае необходимости, вы можете без труда добавить дополнительные опции под конкретные нужды.
План действий
Есть несколько вещей, которые нам нужно сделать, для «превращения» EditText в SearchEditText. Если кратко, то нам нужно:
- Унаследовать SearchEditText от AppCompatEditText
- Добавить иконку «Поиск» в левом (или правом) углу SearchEditText, при нажатии на которую введённый поисковый запрос будет передаваться зарегистрированному слушателю
- Добавить иконку «Очистка» в правом (или левом) углу SearchEditText, при нажатии на которую введённый текст в поисковой строке будет очищаться
- Установить в параметре imeOptions SearchEditText-а значение IME_ACTION_SEARCH, для того, чтобы при появлении клавиатуры кнопка ввода текста выполняла роль кнопки «Поиск»
SearchEditText во всей красе!
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?)
}
}
В приведенном выше коде были использованы две extension-функции для установки правого и левого изображения EditText-а. Эти две функции выглядят следующим образом:
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
)
}
Наследование от 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.
===========
Источник:
habr.com
===========
Похожие новости:
- [IPv6, IT-инфраструктура, Информационная безопасность, Сетевые технологии] The 2020 National Internet Segment Reliability Research
- [Java, Open source, Openshift, Учебный процесс в IT] 10 Kubernetes-инструментов из разряда «важно», шпаргалка по созданию Kubernetes-операторов на Java… и многое другое
- [Браузеры, Разработка под Android] Vivaldi 3.3 для Android — Панельная свобода
- [Информационная безопасность, Разработка под iOS, Разработка под Android, Реверс-инжиниринг, Аналитика мобильных приложений] Домофоны, СКУД… И снова здравствуйте
- [Разработка мобильных приложений, Разработка под Android, Системы обмена сообщениями] Google без предупреждения удалила из магазина приложений почтовый сервис K-9 Mail за разное написание названия
- [Информационная безопасность, Разработка мобильных приложений, Разработка под Android, Софт] В Android 11 появилась встроенная функция записи экрана, а вскоре Google пообещала верифицированные звонки
- [Разработка под Android] Navigation Component-дзюцу, vol. 1 — BottomNavigationView
- Выпуск мобильной платформы Android 11
- [Компьютерная анимация, Разработка под Android] Полируем UI в Android: StateListAnimator (перевод)
- [Виртуализация, Реверс-инжиниринг] Сброс блокировки с устройств Teradici PCoIP Zero Client
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_android, #_edittext, #_android_studio, #_custom_view, #_searchedittext, #_view, #_search, #_listener, #_razrabotka_pod_android (
Разработка под Android
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Пробовали ли Вы когда-нибудь настроить внешний вид или поведение стандартного компонента SearchView? Полагаю, да. В этом случае, я думаю что вы согласитесь, что далеко не все его настройки являются достаточно гибкими, для того, чтобы удовлетворить всем бизнес-требованиям отдельно взятой задачи. Одним из способов решения этой проблемы является написание собственного «кастомного» SearchView, чем мы сегодня и займемся. Поехали! Примечание: создаваемое view (далее – SearchEditText), не будет обладать всеми свойствами стандартного SearchView. В случае необходимости, вы можете без труда добавить дополнительные опции под конкретные нужды. План действий Есть несколько вещей, которые нам нужно сделать, для «превращения» EditText в SearchEditText. Если кратко, то нам нужно:
SearchEditText во всей красе! 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?) } } В приведенном выше коде были использованы две extension-функции для установки правого и левого изображения EditText-а. Эти две функции выглядят следующим образом: 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 ) } Наследование от 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. Нам это необходимо по двум причинам:
Посмотрим на код слушателя: 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. =========== Источник: habr.com =========== Похожие новости:
Разработка под Android ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:20
Часовой пояс: UTC + 5