您是否曾经尝试过自定义标准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提供的所有选项,但是您可以随时通过酌情添加其他选项来改进它。去吧!