最近学了Kotlin这门新语言用于Android开发。其中不免遇上findViewById(),原来自己都是通过ButterKnife 直接进行View的查找。所有难免想用Kotlin的实现。通过网上查找,发现JakeWharton大神已经实现kotterknife。所以我们就直接来分析了。ps:(本文章只讨论基于Kotlin实现的findViewByid,不讨论Anko库)
ButterKnife的使用
1 | private val mIcon by bindView<ImageView>(R.id.iv_image) |
通过以上代码,我们发现。只需要通过 by bindView<View类型>(id)就能获得我们需要找到的View,那其中涉及到的知识点是哪几个呢?
主要知识点
如果你已经对这几个知识点都掌握了,那么就直接开始分析代码,查看bindView方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public fun <V : View> View.bindView(id: Int)
: ReadOnlyProperty<View, V> = required(id, viewFinder)
public fun <V : View> Activity.bindView(id: Int)
: ReadOnlyProperty<Activity, V> = required(id, viewFinder)
public fun <V : View> Dialog.bindView(id: Int)
: ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
public fun <V : View> DialogFragment.bindView(id: Int)
: ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
public fun <V : View> SupportDialogFragment.bindView(id: Int)
: ReadOnlyProperty<SupportDialogFragment, V> = required(id, viewFinder)
public fun <V : View> Fragment.bindView(id: Int)
: ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
public fun <V : View> SupportFragment.bindView(id: Int)
: ReadOnlyProperty<SupportFragment, V> = required(id, viewFinder)
public fun <V : View> ViewHolder.bindView(id: Int)
: ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
我们发现该类中对常用的View、Activity、Dailog、Fragment等都扩展了一个bindView的方法,而其返回值都是Kotlin中标准库中的ReadyOnlyProperty接口。而具体实现是required(id,viewFinder)方法。
而ViewFinder又是什么呢?我们继续查看viewFinder1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private val View.viewFinder: View.(Int) -> View?
get() = { findViewById(it) }
private val Activity.viewFinder: Activity.(Int) -> View?
get() = { findViewById(it) }
private val Dialog.viewFinder: Dialog.(Int) -> View?
get() = { findViewById(it) }
private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
get() = { dialog?.findViewById(it) ?: view?.findViewById(it) }
private val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View?
get() = { dialog?.findViewById(it) ?: view?.findViewById(it) }
private val Fragment.viewFinder: Fragment.(Int) -> View?
get() = { view.findViewById(it) }
private val SupportFragment.viewFinder: SupportFragment.(Int) -> View?
get() = { view!!.findViewById(it) }
private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
get() = { itemView.findViewById(it) }
我们发现ViewFinder只不过是我们常用的View、Activity、Dailog、Fragment等的扩展属性 同时该属性拥有函数类型 T.()->View,并且ViewFinder最终的实现是其相应接受者的findViewById(id)函数。
我们继续点击required方法
1 | @Suppress("UNCHECKED_CAST") |
我们发现required方法返回的是一个Lazy对象,并且初始化了其initializer 属性。根据id寻找相应的View.如果没有找到就执行相应的viewNotFound方法。
可能有些小朋友看不懂这段代码。那么其完整格式为:1
Lazy( { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) })
在Kotlin中有一个约定,如果函数的最后一个参数是一个函数。并且你传递一个Lambda表达式作为相应的参数,你可以在圆括号外面声明它
转换后:1
Lazy(){ t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
注意:如果Lambdashi 该调用的唯一参数。则调用中的圆括号可以省略1
Lazy { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
接下来继续查看Lazy类:1
2
3
4
5
6
7
8
9
10
11
12
13// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
private object EMPTY
private var value: Any? = EMPTY
override fun getValue(thisRef: T, property: KProperty<*>): V {
if (value == EMPTY) {
value = initializer(thisRef, property)
}
@Suppress("UNCHECKED_CAST")
return value as V
}
}
通过观察我们发现该类实现了ReadOnlyProperty接口。我们知道在类委托中。可以实现ReadOnlyProperty(针对于val)或ReadWriteProperty(针对于var)接口。Lazy实现了ReadOnlyProperty那么在属性委托的时候会调用其getValue方法 (第一个参数:当前调用对象。第二个参数。当前属性的描述),在getValue方法中,会调用其 initializer属性的对应的函数作为最终的返回值。
最终。整个流程分析完毕。我们可以发现Kotlin这门语言的强大之处。给我们编码带来了许多方便。还不快来一起学习Koltin。
最后,附上我写的一个基于Kotlin 仿开眼的项目SimpleEyes(ps: 其实在我之前,已经有很多小朋友开始仿这款应用了,但是我觉得要做就做好。所以我的项目和其他的人应该不同,不仅仅是简单的一个应用。但是,但是。但是。重要的话说三遍。还在开发阶段,不要打我),欢迎大家follow和start