在Android中使用Kotlin扩展

在 Java 中如果我们要为类添加新功能,就必须使用继承或者像装饰者这样的设计模式,但是在 Kotlin 中这些可以通过叫做扩展的方式来完成。平时我们开发 Android 的过程中,会逐渐总结出各种各样的工具类,如果使用 Kotlin 则可以通过扩展的方式,来简化或者替代这些工具类。

Kotlin 扩展包括扩展属性、扩展函数 2 种方式,具体语法这里不多说了,主要讲讲通过扩展,在 Android 开发中可以为我们带来哪些便利。

1. 替代View.setOnClickListener方法

在 Java 中为一个 View 设置点击事件,我们必须这样写:

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});

如果一个页面中要为很多 View 设置点击事件,写起来巨繁琐,在 Kotlin 中我们可以为 View 扩展一个函数来轻松实现点击事件:

fun <T : View> T.click(block: (T) -> Unit) {
    setOnClickListener {
        block(this)
    }
}

这个时候,我们为 View 设置点击事件可以这样写:

view.click {
   //处理点击事件逻辑
}

在写法上是不是简单多了,除此之外还可通过扩展属性来实现更高级功能。举个栗子,我们不希望某个按钮被频繁点击,在 Java 中的做法是设置一个变量记录点击时间,如果在某个时间间隔内再次触发点击,则不响应此事件。使用 Kotlin 我们可以这样实现该功能:

先为 View 扩展 2 个属性:

//私有扩展属性,允许2次点击的间隔时间
private var <T : View> T.delayTime: Long
    get() = getTag(0x7FFF0001) as? Long ?: 0
    set(value) {
        setTag(0x7FFF0001, value)
    }

//私有扩展属性,记录点击时的时间戳
private var <T : View> T.lastClickTime: Long
    get() = getTag(0x7FFF0002) as? Long ?: 0
    set(value) {
        setTag(0x7FFF0002, value)
    }   

再为 View 扩展一个方法:

//私有扩展方法,判断能否触发点击事件
private fun <T : View> T.canClick(): Boolean {
    var flag = false
    var now = System.currentTimeMillis()
    if (now - this.lastClickTime >= this.delayTime) {
        flag = true
        this.lastClickTime = now
    }
    return flag
}

//扩展点击事件,默认 500ms 内不能触发 2 次点击
fun <T : View> T.clickWithDuration(time: Long = 500, block: (T) -> Unit) {
    delayTime = time
    setOnClickListener {
        if (canClick()) {
            block(this)
        }
    }
}

在 Kotlin 中使用,我们只需这样调用:

//只有间隔1秒之后,点击才会生效
view.clickWithDuration(1000) {
    //点击事件
}

上面这个例子中,实际上是通过 setTag()/getTag() 来存储 2 个扩展属性真正的值的。这是因为扩展属性并没有将实际的成员插入类中,他们只能由显示提供的 getters/setters 定义。

2. Context的一些扩展

在 Java 中跳转到另一个页面,一般得这样写:

Intent intent = new Intent(context, MainActivity.class);
context.startActivity(intent)

使用 Kotlin 后我们可以这样写:

//使用内联函数的泛型参数 reified 特性来实现
inline fun <reified T : Activity> Context.startActivity() {
    val intent = Intent(this, T::class.java)
    if (this !is Activity) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    startActivity(intent)
}

//这个时候跳转可以这样写,是不是特别简洁
Context.startActivity<MainActivity>()

还有很多我们常用的方法:

//屏幕宽度(px)
inline val Context.screenWidth: Int
    get() = resources.displayMetrics.widthPixels
    
//屏幕高度(px)
inline val Context.screenHeight: Int
    get() = resources.displayMetrics.heightPixels

//屏幕的密度
inline val Context.density: Float
    get() = resources.displayMetrics.density    
    
//dp 转为 px
inline fun Context.dp2px(value: Int): Int = (density * value).toInt()

//dp 转为 px
inline fun Context.dp2px(value: Float): Int = (density * value).toInt()

//px 转为 dp
inline fun Context.px2dp(value: Int): Float = value.toFloat() / density

在实际使用时,这些方法和属性就像类本身定义的一样,我们可以直接拿来使用,非常方便,省去了类似的工具类方法调用。

3. 可空接收者

在 Java 中字符串操作很容易出现空指针异常,要判断一个字符串为空,一般得这样判断:

String str = null;

//对字符串 str 做是否为空判断
if (str == null || str.length() == 0) {

}

在 Kotlin 中我们要判断字符串是否为空,sdk 里直接提供了一个方法 isNullOrEmpty,看源码是通过扩展来实现的,主要代码如下:

public inline fun CharSequence?.isNullOrEmpty(): Boolean {   
    return this == null || this.length == 0
}

该方法与前面扩展函数在形式上的唯一差别是,在类型后面跟着的是?.而不是.,然后在方法体内可以通过this == null来判断调用该方法的对象是否为 null,上面的 Java 代码我们用 Kotlin 来写:

var str = null
//这里再也不需要额外对 str 做非空判断了,可以放心的使用了
if (str.isNullOrEmpty()) {

}

使用这种方式来扩展函数,妈妈再也不用担心空指针异常了。

4. 小结

在 Kotlin sdk 里,可以发现它采用了大量的扩展函数。合理使用扩展,可以大量简化代码,提升开发效率,这个是 Kotlin 中我最喜欢的特性之一。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容