将一个edittext变成一个搜索edittext

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 of SearchEditText,

    SearchEditText的右侧(或左侧)添加一个Clear图标,以便在单击时清除SearchEditText的文本,

  • Set the imeOptions of SearchEditText to IME_ACTION_SEARCH so that when the keyboard appears, instead of an enter button, there is a search button.

    设置imeOptionsSearchEditTextIME_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的构造函数。 这里的defStyleandroidx.appcompat.R.attr.editTextStyle的默认值为androidx.appcompat.R.attr.editTextStyle 。 从LinearLayoutFrameLayout和其他一些视图继承时,我们通常将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 。 发生触摸事件时,我们使用两个辅助函数leftDrawableClickedrightDrawableClicked来查看是否单击了左侧或右侧可绘制对象。

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.

leftDrawableClickedRightDrawableClicked函数非常简单。 以leftDrawableClicked为例。 对于左边的可绘制对象,首先计算startOfDrawableendOfDrawable然后检查触摸点的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值