每个应用都有搜索功能,通常我们在进行搜索时,只要是输入框输入了内容就要执行搜索请求,这样会产生很多次无意义的接口请求,同时用户体验也非常差。遇到这种情况我们的一般的解决思路是:当输入内容超过设定的时间间隔将执行搜索,否则不执行。
需要依赖的类:
YqTextChangeListener.kt
/**
* Created by wangkai on 2021/01/30 10:31
* Desc TextView事件监听
*/
open class YqTextChangeListener : TextWatcher {
override fun afterTextChanged(s: Editable?) {
logger {
"afterTextChanged s=$s"
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
logger {
"beforeTextChanged s=$s; start=$start; after=$after; count=$count"
}
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
logger {
"onTextChanged s=$s; start=$start; before=$before; count=$count"
}
}
}
LifecycleOwerExt.kt
/**
* Context 用于返回LifecycleOwner
*/
fun Context?.getLifecycleOwner(): LifecycleOwner? {
return when (this) {
is Fragment -> {
return this
}
is LifecycleOwner -> {
return this
}
else -> {
null
}
}
}
EditTextExt.kt
```kotlin
@file:JvmName("EditTextExt")
/**
* 搜索间隔
*/
fun EditText?.addTextChangedDebounceListener(debounceTimeOutInMills: Long = 300L, onTextChanged: ((String?) -> Unit)? = null) {
this?.let {
this.addTextChangedListener(YqTextChangeDebounceListener(this, debounceTimeOutInMills) { str: String? ->
onTextChanged?.invoke(str)
})
}
}
/**
* 搜索间隔
*/
fun EditText?.addTextChangedCoroutineDebounceListener(debounceTimeOutInMills: Long = 300L, onTextChanged: ((String?) -> Unit)? = null) {
this?.let {
this.addTextChangedListener(YqTextChangeCoroutineDebounceListener(this, debounceTimeOutInMills) { str: String? ->
onTextChanged?.invoke(str)
})
}
}
我们使用两种方式显示:
Handler
监听EditText的输入,每当文本变化,先检查Handler当前有无未处理的消息,有则移除该消息,然后用sendEmptyMessageDelayed再发送一条延迟消息,如若文本超过延迟时间没有变化,该延迟消息就可以成功执行
/**
* Created by wangkai on 2021/01/30 10:31
* Desc EditText事件搜索监听,使用Handler延迟方式实现
*/
open class YqTextChangeDebounceListener(view: View, private val delayMillis: Long = 300L, onTextChanged: ((String?) -> Unit)? = null) : YqTextChangeListener() {
private var handler: Handler? = null
private val rand by lazy {
Random(100)
}
companion object {
private var HANDLER_WHAT = 0
private const val BUNDLE_KEY = "bundleKey"
}
init {
HANDLER_WHAT = rand.nextInt()
view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
logger {
"onViewAttachedToWindow 感知View状态变化"
}
handler = Handler(Looper.getMainLooper()) {
onTextChanged?.invoke(it.peekData().getString(BUNDLE_KEY))
false
}
}
override fun onViewDetachedFromWindow(v: View?) {
logger {
"onViewDetachedFromWindow 感知View状态变化"
}
handler?.removeCallbacksAndMessages(null)
}
})
}
override fun afterTextChanged(s: Editable?) {
super.afterTextChanged(s)
val editText = s.toString().trim()
if (handler?.hasMessages(HANDLER_WHAT) == true) {
logger {
"handler hasMessages"
}
handler?.removeMessages(HANDLER_WHAT)
}
Message().also {
it.what = HANDLER_WHAT
it.data = Bundle().apply {
putString(BUNDLE_KEY, editText)
}
}.let {
handler?.sendMessageDelayed(it, delayMillis)
}
}
}
注意:因为Handler可能造成内存泄露,所以我使用OnAttachStateChangeListener
通过View的状态变化来监听当前界面是否销毁。防止出现内存泄露
Kotlin协程的StateFlow
/**
* Created by wangkai on 2021/01/30 10:31
* Desc EditText事件搜索监听,使用协程Coroutine的方式实现
*
* 参考自:https://juejin.cn/post/6925304772383735822
*/
@SuppressLint("RestrictedApi")
open class YqTextChangeCoroutineDebounceListener(val view: View, private val delayMillis: Long = 300L, onTextChanged: ((String?) -> Unit)? = null) : YqTextChangeListener(), LifecycleEventObserver {
//定义一个全局的 StateFlow
@ExperimentalCoroutinesApi
private val _etState by lazy { MutableStateFlow("") }
init {
view.context?.getLifecycleOwner()?.also {
it.lifecycle.addObserver(this)//注册自己到activity或fragment的LifecycleRegistry中
}.apply {
this?.lifecycleScope?.launch {
_etState
.debounce(delayMillis) // 限流,500毫秒
// .filter {
// // 空文本过滤掉
// it.isNotBlank()
// }
.collect {
// 订阅数据
logger {
"Thread thread: ${Thread.currentThread()}"
}
onTextChanged?.invoke(it)
}
}
}
}
override fun afterTextChanged(s: Editable?) {
super.afterTextChanged(s)
val editText = s.toString().trim()
// 往流里写数据
_etState.value = editText
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
logger {
"onStateChanged source: $source; event: $event"
}
}
}
使用
editText.addTextChangedDebounceListener { str ->
//do something
}
editText.addTextChangedCoroutineDebounceListener { str ->
//do something
}