Kotlin学习之编写好用的工具方法

1、 来N个数的最大最小值

两个数比大小这个功能,相信每一位开发者都遇到过。如果我想要获取两个数中较大的那个数,除了使用最基本的if 语句之外,还可以借助Kotlin 内置的max() 函数,如下所示:

    val a = 10
    val b = 15
    val larger = max(a,b)

这种代码看上去很简单直观,也很容易理解,因此好像并没有什么优化的必要。

可是现在如果我们想要在3个数中获取最大的那个数,应该怎么写呢?由于max() 函数只能接收两个参数,因此需要先比较前面两个数的大小。然后再拿较大的那个数和剩余的数进行比较,写法如下:

    val a = 10
    val b = 15
    val c = 5
    val largest = max(max(a,b),c)

有没有觉得代码开始变得复杂了呢,那如果是4个数、5个数呢?没错,这个时候你就应该意识到,我们是可以对max() 函数的用法进行简化的。

回顾一下,我们之前中学过vararg 关键字(可变参数列表),它允许方法接收任意多个同等类型的参数,正好满足我们这里的需求。那么我们就可以新建一个Max.kt 文件,并在其中定义一个max() 函数,如下所示:

fun max(vararg nums:Int):Int{
    var maxNum = Int.MIN_VALUE
    for (num in nums) {
        maxNum = max(maxNum,num)
    }
    return maxNum
}

可以看到,这里max() 函数的参数声明中使用了vararg 关键字,也就是说现在它可以接收任意多个整型参数。接着我们使用了一个maxNum 变量来记录所有数的最大值,并在一开始将它赋值变成了整型范围的最小值。然后使用for-in 循环遍历nums 参数列表,如果发现当前遍历的数字比 maxNum 大,就将maxNum 的值更新成这个数,最终将maxNum 返回即可。

仅仅经过这样的一层封装之后,我们在使用max() 函数就会有翻天覆地的变化,比如刚才同样的功能,现在就可以使用如下的写法来实现:

	val a = 10
    val b = 15
    val c = 5
    val largest = max(a, b, c)

这样我们就彻底摆脱了嵌套函数调用的写法,现在不管是求3个数的最大值还是去N个数的最大值,只需要不断第给max() 函数传入参数就可以了。

不过,目前我们自定义的max() 函数还有一个缺点,就是它只能求N个整型数字的最大值,如果我还想求N个浮点型或长整型的最大值,该怎么办呢?当前你可以定义很多个max()函数的重载,来接收不同类型的参数,因为Kotlin 中内置的max() 函数也是这么做的。但是这种方案实现起来过于烦琐,而且还会产生大量的重复代码,因此这里我准备使用一种更加巧妙的做法。

Java 中规定,所有类型的数字都是可以比较的,因此必须实现Comparable 接口,这个规则在Kotlin 中也同样成立。那么我们就可以借助泛型,将max() 函数修改成接收任意多个实现Comparable 接口的参数,代码如下所示:

fun <T : Comparable<T>> max(vararg nums: T): T {
    if (nums.isEmpty()) throw RuntimeException("Params can not be empty.")
    var maxNum = nums[0]
    for (num in nums) {
        if (num > maxNum) {
            maxNum = num
        }
    }
    return maxNum
}

可以看到,这里将泛型T 的上届指定成了Comparable ,那么参数T 就必然是Comparable 的子类型了。接下来,我们判断nums 参数列表是否为空,如果为空的话就主动抛出一个异常,提醒调用者max() 函数必须传入参数。紧接着将maxNum 的值赋值成nums 参数列表中第一个参数的值,然后同样是遍历参数列表,如果发现了更大的值就对maxNum 进行更新。

经过这样的修改之后,我们就可以更加灵活地使用max() 函数了,比如说求3个浮点型数字的最大值,同样也变得轻而易举:

    val a = 3.5
    val b = 3.8
    val c = 4.1
    val largest = max(a,b,c)

而且现在不管是双精度浮点型、单精度浮点型,还是短整型、整型、长整型,只要是实现Comparable 接口的子类型,max() 函数全部支持获取它们最大的值,是一种一劳永逸的做法。

2、 简化Toast 的用法

首先回顾一下Toast 的标准用法吧,如果想要在界面上弹出一段文字提示需要这样写:

    Toast.makeText(context,"This is Toast",Toast.LENGTH_SHORT).show()

是不是很长的一段代码?而且曾经不知道有多少人因为忘记调用最后的show() 方法,导致Toast 无法弹出,从而产生一些千奇百怪的bug。

由于Toast 是非常常用的功能,每次都需要编写这么长的一段代码确实让人很头疼,这个时候你就应该考虑对Toast 的用法进行简化了。

我们来分析一下,Toast 的makeText() 方法接收3个参数:

第一个参数是Toast 提示的上下文环境,必不可少;

第二个参数是Toast 显示的内容,需要由调用方进行指定,可以传入字符串和字符串资源id 两种类型;

第三个参数是Toast 显示的时长,只支持Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG 这两种值,相对来说变化不大。

那么我们就可以给String 类型和Int 类型各添加一个扩展函数,并在里面封装弹出Toast 的具体逻辑。这样以后每次想要弹出Toast 提示时,只需要调用它们的扩展函数就可以了。

新建一个Toast.kt 文件,并在其中编写如下代码:

fun String.showToast(context: Context){
    Toast.makeText(context,this,Toast.LENGTH_SHORT).show()
}
fun Int.showToast(context: Context){
    Toast.makeText(context,this,Toast.LENGTH_SHORT).show()
}

这里分别给String 类和Int 类新增了一个showToast() 函数,并让它们都接收一个Context 函数。然后在函数的内部,我们仍然使用了Toast 原生API 用法,只是将弹出的内容修改成了this,另外将Toast 的显示时长固定设置成了Toast.LENGTH_SHORT。

那么经过这样的扩展之后,我们以后在使用Toast 时可以变得多么简单呢?体验一下就知道了,比如同样弹出一段文字提示可以这么写:

    "This is Toast".showToast(context)

怎么样,比起原生Toast 用法,有没有觉得这种写法顺畅多了呢?另外,这只是弹出一段字符串文本的写法,如果你想弹出一个定义在strings.xml 中的字符串资源,也非常简单,写法如下:

    R.string.app_name.showToast(context)

这两种写法分别调用的就是我们刚才在String 类和 Int 类中添加的showToast() 扩展函数。

当前,这种写法其实还存在一个问题,就是Toast 的显示时长被固定了,如果我现在想要使用Toast.LENGTH_LONG 类型的显示时长怎么办呢?要解决这个问题,其实最简单的做法就是在showToast() 函数中再声明一个显示时长参数,但是这样每次调用showToast() 函数时都要额外多传入一个参数,无疑增加了使用复杂度。

可以通过给函数设定参数默认值的功能,借助这个功能,我们就可以在不增加showToast() 函数使用复杂度的情况下,又让它可以支持动态指定显示时长了。修改Toast.kt 中的代码,如下所示:

fun String.showToast(context: Context,duration:Int = Toast.LENGTH_SHORT){
    Toast.makeText(context,this,duration).show()
}
fun Int.showToast(context: Context,duration:Int = Toast.LENGTH_SHORT){
    Toast.makeText(context,this,duration).show()
}

可以看到,我们给showToast() 函数增加了一个显示时长的参数,但同时也给它指定了一个参数默认值。这样我们之前所使用的showToast() 函数的写法将完全不受影响,默认会使用Toast.LENGTH_SHORT 类型的显示时长。而如果你想要使用Toast.LENGTH_LONG 的显示时长,只需要这样写就可以了:

    "This is Toast".showToast(context,Toast.LENGTH_LONG)

相信我,这样的Toast 工具一定会给你的开发效率带来巨大的提升。

3、简化Snackbar的用法

先来回顾一下Snackbar 的常规用法吧,如下所示:

    Snackbar.make(view,"This is Snackbar",Snackbar.LENGTH_SHORT)
        .setAction("action"){
            // 处理具体的逻辑
        }
        .show()

可以看到,Snackbar 中make() 方法的第一个参数变成了View,而Toast 中的makeText() 方法的第一个参数是Context,另外Snackbar 还可以调用setAction() 方法来设置一个额外的点击事件。除了这些区别之外,Snackbar 和 Toast 的其他用法都是相似的。

由于make() 方法接收一个View参数,Snackbar 会使用这个View 自动查找最外层的布局,用于展示Snackbar,因此,我们就可以给View 类添加一个扩展函数,并在里面封装显示Snackbar 的具体逻辑。新建一个Snackbar.kt 文件,并编写如下代码:

fun View.showSnackbar(text:String, duration:Int = Snackbar.LENGTH_SHORT){
    Snackbar.make(this,text,duration).show()
}
fun View.showSnackbar(resId:Int, duration:Int = Snackbar.LENGTH_SHORT){
    Snackbar.make(this,resId,duration).show()
}

这段代码应该还是很好理解的,和刚才的showToast() 函数比较相似。只是我们将扩展函数添加到了View 类当中,并在参数列表上声明了Snackbar 要显示的内容以及显示的时长。另外,Snackbar 和 Toast 类似,显示的内容也是支持传入字符串和字符串资源id 两种类型的,因此这里我们给showSnackbar() 函数进行了两种参数的函数重载。

现在想要使用Snackbar 显示一段文本提示,只需要这样写就可以了。

    view.showSnackbar("This is Snackbar")

假如Snackbar 没有setAction() 方法,那么我们的简化工作到这里就可以结束了。但是setAction() 方法作为Snackbar 最大的特色之一,如果不能支持的话,我们编写的showSnackbar() 函数也就变得毫无意义了。

这个时候,神通广大的高阶函数就能派上用场了,我们可以让showSnackbar() 函数再额外接收一个函数类型参数,以此来实现Snackbar 的完整功能支持。修改Snackbar.kt 中的代码,如下所示:

fun View.showSnackbar(text:String,actionText:String? = null, duration:Int = Snackbar.LENGTH_SHORT,block: (() -> Unit)?=null{
    val snackbar = Snackbar.make(this, text, duration)
    if (actionText != null && block != null){
        snackbar.setAction(actionText){
            block()
        }
    }
    snackbar.show()
}
                      
fun View.showSnackbar(resId:Int,actionResId:Int?=null, duration:Int = Snackbar.LENGTH_SHORT,block: (() -> Unit)?=null){
    val snackbar = Snackbar.make(this, resId, duration)
    if (actionResId != null && block != null){
        snackbar.setAction(actionResId){
            block()
        }
    }
    snackbar.show()
}

可以看到,这里我们给两个showSnackbar() 函数都增加了一个函数类型参数,并且还增加了一个用于传递给setAction() 方法的字符串或字符串资源id 。这里我们需要将新增的两个参数都设置成可为空的类型,并将默认值都设置成空,然后只有当两个参数都不为空的时候,我们才去调用Snackbar 的setAction() 方法来设置额外的点击事件。如果触发了点击事件,只需要调用函数类型参数将事件传递给额外的Lambda 表达式即可。

这样showSnackbar() 函数就拥有比较完整的Snackbar 功能了,比如本小节最开始的那段示例代码,现在就可以使用如下写法进行实现:

    view.showSnackbar("This is Snackbar","Action"){
        // 处理具体的逻辑
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值