kotlin扩展函数(防抖)、节流

简介

如果说java是开发android的绿皮车,那么kotlin就像是开发android的动车高铁,至于为什么这么说,其中的原因之一:扩展函数

1、首先我们来了解什么是扩展函数?

扩展函数是实现多态的一种形式,一般用于为第三方SDK中的类添加功能方法。不改变原有类的情况下,扩展新的功能。
在这里插入图片描述
Kotlin的扩展函数是“开放-封闭原则”-----对扩展开放,对修改封闭的良好实现。它替代了继承形式的扩展,做到了更优雅的使用和调用,也避免了继承带来的其他问题。

为了能更直观的理解扩展函数,下面展示项目中使用到的示例:
使用结构如下图,其中ActivityExtends,ContextExtends,ImageViewExtends都是扩展函数
在这里插入图片描述
罗列其中的几个功能作为演示

1、在activity或者fragment中弹出菊花等待加载框(背景半透明)

fun Activity.showProgressDialog(context: Context) {
    var mAlertDialog = AlertDialog.Builder(context, R.style.CustomProgressDialog).create()//去除AlertDialog的背景透明
    val loadView = LayoutInflater.from(context).inflate(R.layout.dialog_progressbar_juhua, null)//默认的红色转圈,这里改成自己定义的菊花旋转
    mAlertDialog!!.setView(loadView, 0, 0, 0, 0)
    //        mAlertDialog.setCanceledOnTouchOutside(false);//随便点一下屏幕其他是否消失
    mAlertDialog!!.setCanceledOnTouchOutside(true)//随便点一下屏幕是否消失
    mAlertDialog!!.show()
}

在activity中怎么使用:

showProgressDialog(this)

CustomProgressDialog ,dialog_progressbar_juhua这两个同学们自己定义

2、加载网络图片到imageview

implementation ‘com.github.bumptech.glide:glide:4.7.1’

/**
 * 加载圆形图片
 */
fun ImageView.loadCircleImage(context: Context, path: String, placeholder: Int = R.mipmap.ic_launcher, useCache: Boolean = false) {
    var options = getOptions(placeholder, useCache)
    options.circleCrop()
    Glide.with(context).load(path).apply(options).into(this)
}
private fun ImageView.getOptions(placeholder: Int = R.mipmap.ic_launcher, useCache: Boolean = false): RequestOptions {
    var options = RequestOptions()
    options.placeholder(placeholder)
    options.priority(Priority.HIGH)
    if (useCache)
        options.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
    return options
}

在activity中怎么使用:

//iv_1是imageview的id
 iv_1.loadCircleImage(this, "图片url")

3、在activity或者fragment中弹出提示用户的对话框

fun Context.showAlertDialog(
        title: String,
        message: String,
        ok: Pair<String,()->Unit>,
        cancel: Pair<String,()->Unit>? = null){

    val builder = AlertDialog.Builder(this)
            .setTitle(title)
            .setMessage(message)
            .setCancelable(false)
            .setPositiveButton(ok.first) { _,_ -> ok.second() }

    cancel?.let{
        builder.setNegativeButton(it.first) { _, _ -> it.second() }
    }

    builder.create().show()
}

在activity中怎么使用:

            showAlertDialog(
                    title = "我是标题",
                    message = "我是提示的内容",
                    ok = "Yes" to { test()//点了yes后需要运行的方法},
                    cancel = "No" to { finish() }
            )        

这3个示范是从我自己练习的项目中提取出来,都是可以运行的,方便大家更好的理解。

防抖

  • 防抖:n秒后执行函数,如果n秒内再次触发则重新计时
object DebounceHelper {
    private var debounceJob: Job? = null

    fun debounce(
        delayMs: Long = 500,
        parentScope: CoroutineScope = CoroutineScope(Dispatchers.Main),
        action: suspend CoroutineScope.() -> Unit
    ) {
        debounceJob?.cancel()
        debounceJob = parentScope.launch {
            delay(delayMs)
            action()
        }
    }
}

附加:

项目中用到比较复杂的防抖
实现功能:只要2500ms内mValue的值不发生改变,则会运行传进来的函数,或者总共超时10s也会运行传进来的函数

class Debouncer {
        private var mValue = 0
        private var runnable: Runnable? = null
        private var mAction: (() -> Unit)? = null
        private val handler = Handler(Looper.getMainLooper())
        private val handler2 = Handler(Looper.getMainLooper())

        fun debounce(delay: Long, action: () -> Unit): Debouncer {
            mAction = action
            runnable?.let { handler.removeCallbacks(it) }
            runnable = Runnable {
                mAction?.invoke()
                runnable = null
                mAction = null
                handler.removeCallbacksAndMessages(null)
                handler2.removeCallbacksAndMessages(null)
            }
            handler.postDelayed(runnable!!, delay)
            return this
        }

        fun setValue(value: Int) {
            mValue = value
            if (runnable == null) {
                mAction?.invoke()
                mAction = null
            }
            debounce(delay = 2500, action = mAction ?: {})
        }

        fun actionRun(delay: Long=10000) {
            handler2.postDelayed(runnable!!, delay)
        }

    }

activity中使用:

class MainActivity : AppCompatActivity() {
    private val debouncer = Debouncer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        lifecycleScope.launch {
            repeat(10) {
                Log.i("TAG", "==次数==$it")
                delay(1500)
                debouncer.setValue(it + 1)
            }
        }
    }

    fun gotoShow(v: View) {
        Log.e("TAG", "==按钮点击事件aaa==")
//        DebounceHelper.debounce {
//            runMyMethod()
//        }
        debouncer.debounce(delay = 2500, action = ::runMyMethod).actionRun(5000)
    }
    // 同上 核心类
    class Debouncer {
        private var mValue = 0
        private var runnable: Runnable? = null
        private var mAction: (() -> Unit)? = null
        private val handler = Handler(Looper.getMainLooper())
        private val handler2 = Handler(Looper.getMainLooper())

        fun debounce(delay: Long, action: () -> Unit): Debouncer {
            mAction = action
            runnable?.let { handler.removeCallbacks(it) }
            runnable = Runnable {
                mAction?.invoke()
                runnable = null
                mAction = null
                handler.removeCallbacksAndMessages(null)
                handler2.removeCallbacksAndMessages(null)
            }
            handler.postDelayed(runnable!!, delay)
            return this
        }

        fun setValue(value: Int) {
            mValue = value
            if (runnable == null) {
                mAction?.invoke()
                mAction = null
            }
            debounce(delay = 2500, action = mAction ?: {})
        }

        fun actionRun(delay: Long=10000) {
            handler2.postDelayed(runnable!!, delay)
        }

    }

    fun runMyMethod() {
        Log.e("TAG", "==yyy==")
    }
}

协程版本:

class MainActivity : AppCompatActivity() {
    //    val debounceHelper = DebounceHelper()
    val debouncer = Debouncer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        lifecycleScope.launch {
            repeat(10) {
                Log.i("TAG", "==次数==$it")
                delay(1500)
                debouncer.resetRun()
            }
        }
    }

    fun gotoShow(v: View) {
        Log.e("TAG", "==按钮点击事件aaa==")
        debouncer.debouncer {
            runMyMethod()
        }.mustRun()
    }

    // 注意:这里使用GlobalScope进行演示,正式项目中不要用GlobalScope
    class Debouncer() {
        private var job: Job? = null
        private var job2: Job? = null
        private var block: (() -> Unit)? = null
        private var isAlreadyRun = false

        fun debouncer(mBlock: () -> Unit): Debouncer {
            block = mBlock
            job?.cancel()
            job = GlobalScope.launch {
                delay(2000)
                job2?.cancel()
                mBlock()
            }
            return this
        }

        fun mustRun() { // 10s强制运行block
            job2 = GlobalScope.launch {
                delay(10000)
                job?.cancel()
                block?.invoke()
                isAlreadyRun = true
            }
        }

        fun resetRun() {
            if (isAlreadyRun) {
                return
            }
            block?.let { debouncer(it) }
        }
    }

    fun runMyMethod() {
        Log.e("TAG", "==yyy==")
    }
}

节流

  • 节流:n秒内执行函数,如果n秒内再次触发,也只执行一次

实现Kotlin节流的方法如下:

fun <T> throttleFirst(windowDuration: Long, action: (T) -> Unit): (T) -> Unit {
    var lastTime = 0L
    return {
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastTime > windowDuration) {
            lastTime = currentTime
            action(it)
        }
    }
}

以上代码实现了一个throttleFirst函数,它接受两个参数:windowDuration是时间窗口大小,action是要执行的函数。

当throttleFirst函数被调用时,它返回一个新的函数,该函数用于执行业务逻辑。这个新函数也接受一个参数,当调用它时,它会记录当前时间,并与上一次执行的时间进行比较。如果两次调用的时间间隔超过了时间窗口,就执行业务逻辑,并更新上一次执行的时间。

这样,我们就可以使用throttleFirst函数来限制函数的执行频率。例如,如果我们想要限制点击事件的执行频率为500毫秒,可以这样实现:

val button = findViewById<Button>(R.id.button)
button.setOnClickListener(throttleFirst(500) {
    // 执行点击事件的逻辑
})

这样,在500毫秒内,无论用户点击多少次,业务逻辑都只会被执行一次

附加:使用扩展方式来实现节流

/**
 * View扩展方法统一处理按键延时
 */
fun View.setValidClickListener(block:()->Unit) {
    this.setOnClickListener {
        if (ButtonDelayUtil.isFastClick()){
            block()
        }
    }
}

使用:

mBinding.btCargoSet.setValidClickListener {

        }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wy313622821

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值