Android两种方式实现延迟搜索

2021-11-17  本文已影响0人  kevinsEegets

每个应用都有搜索功能,通常我们在进行搜索时,只要是输入框输入了内容就要执行搜索请求,这样会产生很多次无意义的接口请求,同时用户体验也非常差。遇到这种情况我们的一般的解决思路是:当输入内容超过设定的时间间隔将执行搜索,否则不执行。

需要依赖的类:

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
 }
上一篇下一篇

猜你喜欢

热点阅读