Have you ever tried to customize the appearance or behaviour of aSearchView
? Probably yes. Some customizations are pretty easy to do, but others are not that much straight forward. If you want to have full control, then writing a custom view to replace aSearchView
is one way to go. Writing such a view is not only educational but also fun!
您是否尝试过自定义SearchView
的外观或行为? 可能是。 有些自定义非常容易实现,但是其他自定义则不那么简单。 如果要完全控制,那么编写自定义视图来替换SearchView
是一种方法。 写下这样的观点不仅具有教育意义,而且很有趣!
Note: the custom view (from the hereafter SearchEditText
) we are going to write will not have all the options available by a SearchView
, but adding more options would not be difficult when you get the hang of it.
注意:我们将要编写的自定义视图(此后为SearchEditText
)将不具有SearchView
可用的所有选项,但是,当您掌握了更多内容时,添加更多选项并不困难。
要做的事 (Things to Do)
There are several things we need to do to transform an EditText
into aSearchEditText
. In a nutshell, we need to:
要将EditText
转换为SearchEditText
需要做几件事。 简而言之,我们需要:
Inherit from
AppCompatEditText
,从
AppCompatEditText
继承,Add a Search icon to the left (or right) of
SearchEditText
that when clicked, passes the search query to the registered listener,在
SearchEditText
的左侧(或右侧)添加一个Search图标,单击该图标可将搜索查询传递给已注册的侦听器,Add a Clear icon to the right (or left) of
SearchEditText
so that when clicked, clears the text ofSearchEditText
,在
SearchEditText
的右侧(或左侧)添加一个Clear图标,以便在单击时清除SearchEditText
的文本,Set the
imeOptions
ofSearchEditText
toIME_ACTION_SEARCH
so that when the keyboard appears, instead of an enter button, there is a search button.设置
imeOptions
的SearchEditText
到IME_ACTION_SEARCH
这样,当键盘出现的,而不是输入按钮,还有一个搜索按钮。
In the next section, I will show you the full source code of SearchEditText
, and in the remaining ones, I will walk you through different parts of SearchEditText
to see what each part does.
在下一部分中,我将向您展示SearchEditText
的完整源代码,而在其余部分中,我将SearchEditText
您浏览SearchEditText
不同部分,以了解每个部分的作用。
尽享SearchEditText! (SearchEditText in Full Glory!)
The full source code of SearchEditText
is as follows:
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?)
}
}
In the above code, I’ve used two extension functions to easily set the right and left drawables of an EditText
. These two functions are defined as below:
在上面的代码中,我使用了两个扩展功能来轻松设置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继承 (Inheriting From AppCompatEditText)
There is no secret in inhering from AppCompatEditText
. It is as follows:
从AppCompatEditText
继承没有秘密。 如下:
class SearchEditText
@JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyle: Int = androidx.appcompat.R.attr.editTextStyle
) : AppCompatEditText(context, attributeSet, defStyle)
As you can see I’ve written a constructor and passed the required parameters to the constructor of AppCompatEditText
. The important point here is that the default value of defStyle
is androidx.appcompat.R.attr.editTextStyle
. When inheriting from a LinearLayout
, FrameLayout
, and some other views we usually set 0 as the default value of defStyle
. If we use 0 here, then our SearchEditText
behaves like a TextView
, not an EditText
.
如您所见,我已经编写了一个构造函数,并将所需的参数传递给AppCompatEditText
的构造函数。 这里的defStyle
是androidx.appcompat.R.attr.editTextStyle
的默认值为androidx.appcompat.R.attr.editTextStyle
。 从LinearLayout
, FrameLayout
和其他一些视图继承时,我们通常将0设置为defStyle
的默认值。 如果在此处使用0,则我们的SearchEditText
行为类似于TextView
,而不是EditText
。
处理文本更改事件 (Handling text change events)
The next thing we need to do is to react to the text change events of our SearchEditText
. We need to handle the text change events for two reasons:
我们需要做的下一件事是对SearchEditText
的文本更改事件作出React。 我们需要处理文本更改事件有两个原因:
to show or hide the Clear icon based on whether the text of
SearchEditText
is empty or not,根据
SearchEditText
的文本是否为空来显示或隐藏“清除”图标,to inform the listener, if any, that the text of
SearchEditText
has changed.通知侦听器(如有)
SearchEditText
的文本已更改。
The following code shows the definition of setTextChangeListener
function.
以下代码显示了setTextChangeListener
函数的定义。
private fun setTextChangeListener() {
doAfterTextChanged {
if (it.isNullOrBlank()) {
setRightDrawable(0)
} else {
setRightDrawable(android.R.drawable.ic_menu_close_clear_cancel)
}
queryTextListener?.onQueryTextChange(it.toString())
}
}
To handle the text change events I’ve used the doAfterTextChanged
extension function provided byandroidx.core:core-ktx
library.
为了处理文本更改事件,我使用了androidx.core:core-ktx
库提供的doAfterTextChanged
扩展功能。
处理搜索按钮按下 (Handling the Search button press)
When user presses an action key on the keyboard we check to see if the action is IME_ACTION_SEARCH
. If that’s the case then we inform the listener about this action and pass it the text of SearchEditText
. The following code shows how it’s done.
当用户在键盘上按下操作键时,我们将检查操作是否为IME_ACTION_SEARCH
。 如果是这种情况,那么我们会将此操作通知给侦听器,并向其传递SearchEditText
的文本。 以下代码显示了它是如何完成的。
private fun setOnEditorActionListener() {
setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
queryTextListener?.onQueryTextSubmit(text.toString())
true
} else {
false
}
}
}
处理搜索和清除图标的单击事件 (Handling the click events of Search and Clear icons)
Last but not least we should take care of click events of Search and Clear icons. The tricky part here is that the drawables of an EditText
don’t respond to click events, meaning that there is no official listener to register to get notified when they are clicked. So we need to find a way to detect if they are clicked.
最后但并非最不重要的一点是,我们应该注意“搜索”和“清除”图标的单击事件。 这里最棘手的部分是EditText
的可绘制对象不响应单击事件,这意味着没有官方的侦听器可以注册以在单击它们时获得通知。 因此,我们需要找到一种方法来检测它们是否被点击。
To do so we register a OnTouchListener
on the SearchEditText
. When a touch event occurs we use two helper functions leftDrawableClicked
and rightDrawableClicked
to see if the left or right drawable is clicked.
为此,我们在OnTouchListener
上注册一个SearchEditText
。 发生触摸事件时,我们使用两个辅助函数leftDrawableClicked
和rightDrawableClicked
来查看是否单击了左侧或右侧可绘制对象。
Take a look at the following 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
}
}
The leftDrawableClicked
and RightDrawableClicked
functions are pretty straight forward. Take leftDrawableClicked
for instance. For the left drawable first we compute startOfDrawable
and endOfDrawable
then we check to see if the x
coordinate of the touched point is within the range [startofDrawable, endOfDrawable]
. If that’s the case it means the left drawable is clicked. rightDrawableClicked
function works in a similar manner.
leftDrawableClicked
和RightDrawableClicked
函数非常简单。 以leftDrawableClicked
为例。 对于左边的可绘制对象,首先计算startOfDrawable
和endOfDrawable
然后检查触摸点的x
坐标是否在[startofDrawable, endOfDrawable]
范围内。 如果是这种情况,则意味着单击了左侧可绘制对象。 rightDrawableClicked
函数以类似的方式工作。
Based on whether the left or right drawable is clicked we take proper action. If the left drawable (Search icon) is clicked we inform the listener by calling its onQueryTextSubmit
function. If the right drawable is clicked we clear the text of SearchEditText
.
根据是单击左侧还是右侧可绘制对象,我们将采取适当的措施。 如果单击左侧可绘制对象(搜索图标),我们将通过调用其onQueryTextSubmit
函数来通知侦听器。 如果单击了正确的drawable,我们将清除SearchEditText
的文本。
摘要 (Summary)
In this post, we took some simple steps to turn an EditText
into a SearchEditText
. As mentioned before, SearchEditText
does not support all the options provided by a SearchView
but adding more options would not be difficult at all. Trust me!
在本文中,我们采取了一些简单的步骤将EditText
转换为SearchEditText
。 如前所述, SearchEditText
不支持SearchView
提供的所有选项,但是添加更多选项根本不会很困难。 相信我!
P.S: You can access the source code of SearchEditText
from this GitHub repository.
PS:您可以访问的源代码SearchEditText
从这个 GitHub的仓库。
翻译自: https://medium.com/@masood.fallahpoor/turning-an-edittext-into-a-searchedittext-ddef220f6b43