搜索
您的当前位置:首页正文

Anko的源码解析

来源:二三娱乐

前言


verticalLayout {
   val name = editText()
   button("Say Hello") {
     onClick { toast("Hello, ${name.text}!") 
  } 
}}

例如上述代码简单粗暴,光鲜亮丽的背景下是怎么实现的呢?咱们来刨根问底

准备


为什么能在LinearLayout,RelativeLayout里面调用textView( ){ },button(){ }等方法?
这些都是Anko给ViewManager加的扩展方法,所以在ViewManager内部直接调用这些方法。刚刚上面介绍了ViewManager是所有容器实现的接口,所以在容器内部可以调用这些方法。

3. 别被闭包的简洁写法搞晕了

inline fun aaa( init : B.() -> Unit) : B {
    val b = B()
    b.init()
    return b
}

aaa方法参数也是个函数,这个参数可以理解为 在B类中扩展个匿名方法或者代码块。

调用这个方法

aaa {
    ccc( this )
}

源码


布局的代码工作流程是什么呢?

Paste_Image.png

1.创建控件:anko都是定义单例,可以看下面代码
2.调用init(): 这个函数参数是我们传入的
3.addView(): 它根据传入的上下文对象,如果是acvtivity就setContentView(), 如果ViewManager就addView()。这就关系后面咱们讨论anko有哪些坑里面会说到的一些问题

verticalLayout点进去,看到如下代码

inline fun Activity.verticalLayout(): LinearLayout = verticalLayout({
})

inline fun Activity.verticalLayout(init: _LinearLayout.() -> Unit): LinearLayout {
    return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, init)
}

verticalLayout() 其实是个函数,参数也是个函数。_LinearLayout是LinearLayout的子类,这个咱们后面讲的lparam时候再说。这个方法返回是一个LineaerLayout,咱们来看看他的代码是怎么生成LinearLayout。
ankoView($$Anko$Factories$CustomViews.VERTICAL_LAYOUT_FACTORY, init)

object `$$Anko$Factories$CustomViews` {
    val VERTICAL_LAYOUT_FACTORY = {ctx: Context ->
        val view = _LinearLayout(ctx) 
        view.orientation = LinearLayout.VERTICAL
        view
    }}

创建一个单例工厂类,里面有个函数属性:

val VERTICAL_LAYOUT_FACTORY:(Context)-> _LinearLayout

里面的代码很简单,这里就不说了。
最关键的是ankoView是什么,咱们点进去看看(代码如下:)

inline fun <T : View> Activity.ankoView(factory: (ctx: Context) -> T, init: T.() -> Unit): T {
    val view = factory(this)
    view.init()
    AnkoInternals.addView(this, view)
    return view
}

ankoView是activity扩展的一个方法,需要2个参数。
val view = factory(this) 通过工厂类构建这个控件
view.init() 控件初始化做一些动作 然后返回

最关键的是 AnkoInternals.addView(this, view) 是干啥呢?咱们点进去再看代码

fun <T : View> addView(activity: Activity, view: T) {
    createAnkoContext(activity, { AnkoInternals.addView(this, view) }, true)
}

fun <T : View> addView(manager: ViewManager, view: T) {
    return when (manager) {
        is ViewGroup -> manager.addView(view)
        is AnkoContext<*> -> manager.addView(view, null)
        else -> throw AnkoException("$manager is the wrong parent")
    }
}

inline fun <T> T.createAnkoContext(
        ctx: Context,
        init: AnkoContext<T>.() -> Unit,
        setContentView: Boolean = false): AnkoContext<T> {
    val dsl = AnkoContextImpl(ctx, this, setContentView)
    dsl.init()
    return dsl
}

我把3段调用的代码都贴出来了。
调用的第一个函数很简单,就是调用createAnkoContext(...)方法。
这个方法需要三个参数,主要是第二个:init: AnkoContext<T>.() -> Unit 也是个函数,他是怎么传这个参数呢 { AnkoInternals.addView(this, view) },调用了上述代码的第二个方法,这里面的this 就是AnkoContext<T>,其实就是 AnkoContextImpl <_LinearLayout>,第二个方法很简单,就是调用他们的addView方法

AnkoContextImpl是什么呢?咱们看看他的代码,看看他addView()干了啥

open class AnkoContextImpl<T>(
        override val ctx: Context,
        override val owner: T,
        private val setContentView: Boolean) : AnkoContext<T> {
        private var myView: View? = null
        override val view: View
        get() = myView ?: throw IllegalStateException("View was not set previously")

      override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
        if (view == null)return

        if (myView != null) {
            alreadyHasView()
        }

        this.myView = view

        if (setContentView) {
            doAddView(ctx, view)
        }
    }

    private fun doAddView(context: Context, view: View) {
        when (context) {
            is Activity -> context.setContentView(view)
            is ContextWrapper -> doAddView(context.baseContext, view)
            else -> throw IllegalStateException("Context is not an Activity, can't set content view") 
       }
    }

    open protected fun alreadyHasView(): Unit = throw IllegalStateException("View is already set: $myView")
}

这么多代码其实就干了一件事情,就是 is Activity -> context.setContentView(view)

其他控件和verticalLayout差不多。

那咱们在来看看lparams是怎么实现

  textView {    

  }.lparams(WRAP_CONTENT, WRAP_CONTENT) {
    centerHorizontally()
    below(circleImgView)
    topMargin = kIntHeight(0.02f)
}

首先咱们要理解textView { }这个方法返回是 TextView控件对象。
然后lparams是控件里面的一个方法,看下面代码

fun <T : View> T.lparams( 
       width: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        height: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        init: RelativeLayout.LayoutParams.() -> Unit = {}): T {
    val layoutParams = RelativeLayout.LayoutParams(width, height)
    layoutParams.init()
    this@lparams.layoutParams = layoutParams
    return this
}

看到这里其实大家应该都明白,Anko简洁的写法大致是怎么实现。

下篇文章我会大致和大家讲讲:实际使用过程中遇到一些坑。

Top