参考链接:《聊一聊Kotlin扩展函数run,with,let,also和apply的使用和区别》
前面几篇我们了解了kotlin的语法和概念的东西,接下来我们在实际使用中看看kotlin带给我们的惊喜。
扩展函数的利用
现象
先来看一段代码:
1
2
3
| val avaliableTime: String?
avaliableTime = R.string.avaliable_time.resToString("3年")
println(avaliableTime)
|
其中 avaliable_time 资源的定义如下
1
| <string name="avaliable_time">有效期%s</string>
|
最终打印结果为:有效期3年
上面这个特点就是Kotlin扩展函数带给我们的福利。
实现原理
新建文件Extends.kt
1
| fun Int.resToString(vararg params: Any): String = WangApplication.RESOURCES.getString(this, *params)
|
实际上这里是对Int对象的方法进行了扩展,扩展了一个resToString方法来方便fromat字符串。
拓展
处理对字符串处理可以添加扩展函数外,我们也可以对dp和px转换、显示toast等也添加扩展函数。
Extends.kt文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
| fun Int.resToColor(): Int = ResourcesCompat.getColor(WangApplication.RESOURCES, this, null)
fun Int.resToString(vararg params: Any): String = WangApplication.RESOURCES.getString(this, *params)
@JvmOverloads
fun Int.inflate(viewGroup: ViewGroup? = null, attachView: Boolean = false, context: Context = WangApplication.CONTEXT): View = LayoutInflater.from(context).inflate(this, viewGroup, attachView)
fun Float.dpToPx(): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, WangApplication.RESOURCES.displayMetrics)
fun Float.spToPx(): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this, WangApplication.RESOURCES.displayMetrics)
fun Int.resToDrawable(): Drawable = ResourcesCompat.getDrawable(WangApplication.RESOURCES, this, null)!!
fun Activity.toast(message: String) {
this.runOnUiThread {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
fun Activity.toast(@StringRes message: Int) {
this.runOnUiThread {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
fun Calendar.formatTo(formatter: SafeSimpleDateFormat): String {
return formatter.format(this.timeInMillis)
}
fun Activity.hideKeyBoard() {
val inputMethodManager = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(this.window.decorView.windowToken, 0)
}
infix fun <T> Boolean.then(value: T?) = if (this) value else null
fun <T> Boolean.then(value: T?, default: T) = if (this) value else default
inline fun <T> Boolean.then(function: () -> T, default: T) = if (this) function() else default
inline fun <T> Boolean.then(function: () -> T, default: () -> T) = if (this) function() else default()
inline infix fun <reified T> Boolean.then(function: () -> T) = if (this) function() else null
fun Bundle.put(params: Array<out Pair<String, Any?>>): Bundle {
params.forEach {
val key = it.first
val value = it.second
when (value) {
is Int -> putInt(key, value)
is IntArray -> putIntArray(key, value)
is Long -> putLong(key, value)
is LongArray -> putLongArray(key, value)
is CharSequence -> putCharSequence(key, value)
is String -> putString(key, value)
is Float -> putFloat(key, value)
is FloatArray -> putFloatArray(key, value)
is Double -> putDouble(key, value)
is DoubleArray -> putDoubleArray(key, value)
is Char -> putChar(key, value)
is CharArray -> putCharArray(key, value)
is Short -> putShort(key, value)
is ShortArray -> putShortArray(key, value)
is Boolean -> putBoolean(key, value)
is BooleanArray -> putBooleanArray(key, value)
is SparseSerializableArray<*> -> putSerializable(key, value)
is Serializable -> putSerializable(key, value)
is Parcelable -> putParcelable(key, value)
is Bundle -> putAll(value)
is Array<*> -> when {
value.isArrayOf<Parcelable>() -> putParcelableArray(key, value as Array<out Parcelable>?)
}
}
}
return this
}
|
延迟创建对象实例
lazy
lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行。
1
2
3
4
5
6
7
8
9
| private val adapter by lazy {
object : BaseQuickAdapter<OverageObject, BaseViewHolder>(R.layout.item_qrcode_quick_coupon) {
override fun convert(helper: BaseViewHolder, item: OverageObject) {
helper.setText(R.id.create_time, TimeFormat.formatTotal(item.insertTime.toLong()))
.setText(R.id.available_time, R.string.avaliable_time.resToString(Formats.daySecond(item.scanValidPeriod / 60)))
.setText(R.id.coupon_value, convertCoupon(item))
}
}
}
|
我们一般在Java的类中初始化成员变量需要根据时机才选择初始化代码的位置,但是在kotlin中完全不用担心这个问题,我们可以使用by lazy来延迟初始化对象,例如上面的adapter对象就是延迟初始化的。
还有一种场景,比如多次使用同一个对象,只获取不赋值。
1
2
3
| private val mUserMannager: UserMannager by lazy {
UserMannager.getInstance()
}
|
上面的object: 类似于Java中的匿名内部类的实现,具体可参考《kotlin中的object关键字》
lateinit
lateinit 则用于只能生命周期流程中进行获取或者初始化的变量,我们在Java中习惯先定义后初始化,例如下面代码。
1
2
3
4
5
6
7
| class Demo {
var value: String
fun printValue() {
println(value)
}
}
|
这样的代码在kotlin中会报错:Property must be initialized or be abstract
1
2
3
4
5
6
7
8
9
| class Demo {
lateinit var value: String //添加lateinit关键字
fun printValue() {
println(value)
}
}
|
运行后会发现还是报错:Exception in thread “main” kotlin.UninitializedPropertyAccessException: lateinit property value has not been initialized
lateinit 的蛋疼之处在于,萌新以为找到了接近 Java 的写法,但实际上一只脚已经踩在了地雷上;有一定 Kotlin 经验的同学呢,要么觉得 lateinit 给的自由度完全不够,必要的时候直接 “var + 可空类型”浪起,要么处处担惊受怕,生怕碰到没有初始化的属性。这样,lateinit 就变成一个十足的鸡肋了,会用的不想用,不会用的处处掉坑。
对于 Kotlin 新手来说,应该抛开 Java 式的写法,牢记类属性的三种初始化方式:
- 主构造函数内定义属性,使用传入的参数初始化属性;
- 类体内定义属性,同时初始化;
- 类体内定义属性,init 块里初始化。
特殊函数
run函数
调用run函数块。返回值为函数块最后一行,或者指定return表达式。
1
2
3
4
5
6
7
8
| fun test(){
var animal = "cat"
run {
val animal = "dog"
println(animal) // dog
}
println(animal) //cat
}
|
在这个简单的test函数当中我们拥有一个单独的作用域,在run函数中能够重新定义一个animal变量,并且它的作用域只存在于run函数当中。
例如现在有这么一个场景,用户领取app的奖励,如果用户没有登录弹出登录dialog,如果已经登录则弹出领取奖励的dialog。我们可以使用以下代码来处理这个逻辑。
1
2
3
| run {
if (islogin) loginDialog else getAwardDialog
}.show()
|
可以看到上面这段代码会变得更加的简洁,并且可以将show方法一次应用到上面两个dialog当中,而不是去调用两次。
let函数
默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
1
2
3
4
5
6
7
8
9
| RetrofitService.create(ApiService::class.java)
.testRequest(params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : MessagedAction<JSONResponse>(view) {
override fun onSuccess(response: JSONResponse) {
view.doTestChange()
}
}).let { watchSubscription(it) }
|
例如上面我们使用Retrofit请求testRequest接口,然后使用let函数,此时it代表的是Subscription对象(获取调用者本身)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| @Override
public void watchSubscription(Subscription subscription) {
recyclerSubscriptions();
mSubscriptions.add(new WeakReference<>(subscription));
}
@Override
public final void unsubscribeAll() {
for (WeakReference<Subscription> ref : mSubscriptions) {
Subscription subscription = ref.get();
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
mSubscriptions.clear();
}
|
在当前Activity引用所有Subscription并在onDestroy的时候调用unsubscribeAll()方法来取消和释放所有请求。
apply函数
调用某对象的apply函数,在函数块内可以通过 this 指代该对象。返回值为该对象自己。
1
2
3
4
5
6
| fun toOtherPage(activity: Activity, plateObject: PlateObject?): Intent {
return Intent(activity, OtherActivity::class.java).apply {
putExtra(Constant.Key.IS_FILL_CAR_TIME, true)
putExtra(Constant.Key.CAR_IN_TIME, plateObject)
}
}
|
可以使用apply函数对一些创建的特定对象进行进一步的操作和包装,如上面的例子就是对Intent设置传入参数。所以一般对象初始化可以使用apply函数。
also函数
调用某对象的also函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。
可以使用also函数将整块逻辑分割开来。
1
2
3
4
5
| Object.also{
//do some thing 1
}.also{
//do some thing 2
}
|
with函数
with函数和前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。
1
2
3
4
5
| val a = with("string") {
println(this)
3
}
println(a)
|
运行结果
通常我们使用with对一个对象实例调用多个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { // 画一个 100 像素的正方形
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
|
工具类创建
在kotlin中创建工具类有两种方法,一种是使用object关键字,一种是全局函数。
object关键字
1
2
3
4
5
6
7
8
9
10
| object AppUtil {
/**
* 获取meta-data的value值
*/
fun getMetaData(key: String): String{
return CustomeApplication.instance.packageManager.getApplicationInfo(
BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA).metaData.getString(key)
}
}
|
我们反编译后decompil之后的Java代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public final class AppUtil {
public static final AppUtil INSTANCE;
@NotNull
public final String getMetaData(@NotNull String key) {
Intrinsics.checkParameterIsNotNull(key, "key");
String var10000 = CustomeApplication.Companion.getInstance().getPackageManager().getApplicationInfo("com.parkingwang.irain.airporttaxi", 128).metaData.getString(key);
Intrinsics.checkExpressionValueIsNotNull(var10000, "CustomeApplication.insta…).metaData.getString(key)");
return var10000;
}
static {
AppUtil var0 = new AppUtil();
INSTANCE = var0;
}
}
|
可以看到object关键字实际上对应的是Java中的单例模式。
全局函数
我们新建文件Ext.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| package com.xxx.airporttaxi.util
import android.content.pm.PackageManager
import com.xxx.airporttaxi.BuildConfig
import com.xxx.airporttaxi.CustomeApplication
/**
* 描述:
*
* @author 李小强 (lxq_xsyu@163.com)
* @date 2019/3/6
*/
/**
* 获取meta-data的value值
*/
fun getMetaData(key: String): String{
return CustomeApplication.instance.packageManager.getApplicationInfo(
BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA).metaData.getString(key)
}
|
我们反编译后decompil之后的Java代码如下
1
2
3
4
5
6
7
8
9
| public final class ExtKt {
@NotNull
public static final String getMetaData(@NotNull String key) {
Intrinsics.checkParameterIsNotNull(key, "key");
String var10000 = CustomeApplication.Companion.getInstance().getPackageManager().getApplicationInfo("com.xxx.airporttaxi", 128).metaData.getString(key);
Intrinsics.checkExpressionValueIsNotNull(var10000, "CustomeApplication.insta…).metaData.getString(key)");
return var10000;
}
}
|
可以看出来实际上全局函数最后会自动生成该kt文件命名的一个类,函数会成为静态成员函数。
单例VS静态方法
单例和静态方法都可以方便我们全局的使用方法,这两个有什么区别呢?
单例的特点:
- 可以延迟初始化
- 可以继承、实现接口
- 方法可以被覆写
- 可以维持状态(成员变量)
- 一直存在于内存,不会被GC
- 相对于静态方法更加面向对象
静态方法的特点:
- 静态方法中的对象会在方法执行完后释放
- 不需要实例化静态方法所在的类
- 静态方法更加适用于一些工具类
根据EditText内容来改变Button透明度
有的时候我们需要在用户全部输入后将按钮的透明度改为100%不透明,在未输入完成的时候透明度为50%。在kotlin中我写了一个非常好用的工具方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| fun addButtonEmptyAlpha(alphaButton: Button, vararg texts: TextView){
alphaButton.alpha = 0.5f
texts.forEach {
it.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) { }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
var hasEmpty = false
texts.forEach { if(it.text.isEmpty()) hasEmpty = true }
if(hasEmpty){
alphaButton.alpha = 0.5f
}else{
alphaButton.alpha = 1f
}
}
})
}
}
|
该方法的第一个参数就是需要改变透明度的按钮对象,第二个参数是Kotlin中的可变参数(vararg来声明)可以传入多个TextView对象。
连续使用了两次forEach{ }来遍历集合。
1
| AppUtil.addButtonEmptyAlpha(registSubmit, registPhoneEdit, registCheckCodeEdit, registPwdEdit, registPwdReEdit)
|