将EditText转换为SearchEditText

图片



您是否曾经尝试过自定义标准SearchView组件的外观或行为?大概吧。在这种情况下,我认为您会同意,并非所有设置都具有足够的灵活性来满足单个任务的所有业务需求。解决此问题的方法之一是编写自己的“自定义” SearchView,我们今天将做。走!



注意:创建的视图(以下称为SearchEditText)将不具有标准SearchView的所有属性。如有必要,您可以轻松添加其他选项以满足特定需求。



行动计划



我们需要做几件事才能将EditText转换为SearchEditText。简而言之,我们需要:



  • 从AppCompatEditText继承SearchEditText
  • 单击时,在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?)
    }

}


在上面的代码中,使用了两个扩展功能来设置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和其他一些视图,我们倾向于将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())
    }
}


为了处理文本更改事件,使用了androidx.core中的doAfterTextChanged扩展函数:core-ktx。



处理键盘上的Enter按钮的单击



当用户按下键盘上的Enter键时,将检查该动作是否为IME_ACTION_SEARCH。如果是这样,则我们将此操作通知给侦听器,并将文本从SearchEditText传递给它。让我们看看这是怎么发生的。



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


处理图标点击



最后,最后但并非最不重要的问题-如何处理单击搜索图标和清除文本的操作。这里的问题是,默认情况下,标准EditText的可绘制对象不响应单击事件,这意味着没有官方的侦听器可以处理这些事件。



为了解决此问题,在SearchEditText中注册了一个OnTouchListener。触摸时,通过使用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提供的所有选项,但是您可以随时通过酌情添加其他选项来改进它。去吧!



PS:

您可以访问为SearchEditText源代码GitHub的仓库。



All Articles