Anko原理

ViewManager

在了解anko之前,我们必须要先了解一下ViewManager这个类,这个是一个接口,通过这个接口,我们可以在Activity中添加、移除和更新View,我们可以通过 Context.getSystemService()来或者这个类。

public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);

也就是说,只有实现了这个接口的类才能够在activity中对view进行操作。系统的ViewGroup就实现了这个接口。

AnkoContext

在anko中,所有的View的操作是通过AnkoContext这个类来实现 的,所以,AnkoContext实现了ViewManager接口,但是AnkoContext只提供添加功能,不提供view的移除和更新。

interface AnkoContext<out T> : ViewManager {
    val ctx: Context
    val owner: T
    val view: View

    override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
        throw UnsupportedOperationException()
    }

    override fun removeView(view: View) {
        throw UnsupportedOperationException()
    }

一旦我们调用了updateViewLayout或者removeView方法,那么将会抛异常,UnsupportedOperationException,不支持这种操作。
这个类中包含了下面的三个参数

  • ctx: Context-- 上下文信息
  • owner: T-- 这个owner是这个UI的依附者,可能是Activity、fragment、viewHolder
  • view: View--AnkoComponent生成并返回的View
    下面来看看ankoContext提供的几个静态方法:
companion object {
        fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
                = AnkoContextImpl(ctx, ctx, setContentView)

        fun createReusable(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
                = ReusableAnkoContext(ctx, ctx, setContentView)

        fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
                = AnkoContextImpl(ctx, owner, setContentView)

        fun <T> createReusable(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
                = ReusableAnkoContext(ctx, owner, setContentView)

        fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
    }

这4个方法返回的都是AnkoContext实体,那么区别是什么?

  • create表示直接创建出AnkoContext,并且不能重用,一旦已经绑定了View,那么将抛出异常。
  • createReusable表示创建出可以复用的AnkoContext,如果一个AnkoContext已经添加了子View,那么它会重新add View
  • createDelegate;表示将view添加到相应的委托对象中,用来在自定义View中代替inflate方法。
    我们来看看这几个方法的实现:

AnkoContextImpl

create方法返回的是AnkoContextImpl

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")

    //将View添加到context中
    override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
        
        if (view == null) return

        if (myView != null) {
            //如果myView!=null,表示已经添加了View了,如果是create方法调用的,那么将会抛出异常
            alreadyHasView()
        }

        this.myView = view

        if (setContentView) {
            //如果需要setContentView,那么执行addView操作
            doAddView(ctx, view)
        }
    }

    private fun doAddView(context: Context, view: View) {
        when (context) {
            //找到activity,然后执行setContentView
            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")
}

ReusableAnkoContext

createReusable返回的是ReusableAnkoContext实例

internal class ReusableAnkoContext<T>(
        override val ctx: Context,
        override val owner: T,
        setContentView: Boolean
) : AnkoContextImpl<T>(ctx, owner, setContentView) {
    override fun alreadyHasView() {}
}

因为createReusable表示创建的是可重用布局,而AnkoContextImpl在已经绑定了View的时候,将会通过alreadyHasView抛出异常。所以ReusableAnkoContext通过复写alreadyHasView,并且来让布局可重用。

DelegatingAnkoContext

createDelegate(owner: T)返回的是DelegatingAnkoContext实例

internal class DelegatingAnkoContext<T: ViewGroup>(override val owner: T): AnkoContext<T> {
    override val ctx: Context = owner.context
    override val view: View = owner

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

        if (params == null) {
            owner.addView(view)
        } else {
            owner.addView(view, params)
        }
    }
}

DelegatingAnkoContext会将view添加到owner上,而不是Activity。

AnkoComponen

在Anko中,如果我们想要它的预览特性,那么我们就需要用到AnkoComponen,只有继承了AnkoComponen,并且结合anko support,就能�够预览了,我们需要在实现createView方法

interface AnkoComponent<in T> {
    fun createView(ui: AnkoContext<T>): View
}

我们通过createView方法绘制我们想要的View并返回。AnkoComponent有一个扩展方法setContentView,是用来给Activity设置ContentView的。

fun <T : Activity> AnkoComponent<T>.setContentView(activity: T): View =
        createView(AnkoContextImpl(activity, activity, true))

扩展

AnkoContext内部添加了几个扩展函数

inline fun Context.UI(setContentView: Boolean, init: AnkoContext<Context>.() -> Unit): AnkoContext<Context> =
        createAnkoContext(this, init, setContentView)

inline fun Context.UI(init: AnkoContext<Context>.() -> Unit): AnkoContext<Context> =
        createAnkoContext(this, init)

inline fun Fragment.UI(init: AnkoContext<Fragment>.() -> Unit): AnkoContext<Fragment> =
        createAnkoContext(activity, init)

在Activity和fragment中,可以直接通过UI的方式调用。

 UI { 
    .....
    }

然后内部会调用createAnkoContext,将UI里面的View传入

 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
    }

内部还是调用AnkoContextImpl,并且调用内部还是调用AnkoContextImpl的init方法,初始化UI,并添加ctx中.

AnkoUI布局的动态创建

在了解AnkoUI的布局的创建之前,我们需要先了解一下anko支持的dsl.

DSL(Domain-Specific-Language)

dsl指的是特定领域的语言,kotlin的DSL特性支持就是扩展,anko通过dsl,才有了anko layout库。

带接收者的函数字面值

在kotlin中,支持给函数指定接收者对象,而无需额外的限定符,有点类似于扩展函数。

init: (@AnkoViewDslMarker _RelativeLayout).() -> Unit

相当于() -> Unit指定的接收者对象为_RelativeLayout
如果在函数体内部可以调用接收者对象的方法,那么假若这个方法又是带接收者类型的方法,那么就可以不断的往下调用了。

anko布局

下面是一个简单的anko布局

relativeLayout {
                imageView {
                    adjustViewBounds = true
                    scaleType = ImageView.ScaleType.CENTER_CROP
                    imageResource = R.drawable.bg_members
                }.lparams(width = matchParent, height = matchParent)
                statusBar = view {
                    id = statusBarHolder
                }

anko给ViewManager添加了大部分组件的扩展函数,这个根节点relativeLayout将会调用到扩展函数中,

inline fun ViewManager.relativeLayout(): android.widget.RelativeLayout = relativeLayout() {}
inline fun ViewManager.relativeLayout(init: (@AnkoViewDslMarker _RelativeLayout).() -> Unit): android.widget.RelativeLayout {
    return ankoView(`$$Anko$Factories$Sdk15ViewGroup`.RELATIVE_LAYOUT, theme = 0) { init() }
}

在上面这个relativeLayout方法中,接收一个() -> Unit的lambada表达式,这个表达式限定于relativeLayout,所以这个参数就是relativelayout里面的元素,在上面的例子就是这个方法:

imageView {
        adjustViewBounds = true
        scaleType = ImageView.ScaleType.CENTER_CROP
        imageResource = R.drawable.bg_members
        }.lparams(width = matchParent, height =matchParent){
            statusBar = view {
            id = statusBarHolder
        }

有一个注解AnkoViewDslMarker,这个参数会给对应_RelativeLayout的View对象扩展一个applyRecursively方法


@DslMarker
@Target(AnnotationTarget.TYPE)
annotation class AnkoViewDslMarker

/**
 * Apply [f] to this [View] and to all of its children recursively.
 * 
 * @return the receiver.
 */
inline fun <T : View> T.applyRecursively(noinline f: (View) -> Unit): T {
    AnkoInternals.applyRecursively(this, f)
    return this
}

applyRecursively扩展T对象,并且接收一个f函数,在这里指的是View的init方法,会先执行f方法,然后遍历所有的子元素,并进行创建。

 fun applyRecursively(v: View, style: (View) -> Unit) {
        //执行init方法,创建对象
        style(v)
        if (v is ViewGroup) {
        //如果是ViewGroup,那么可以添加子View,看看是否有子View
            val maxIndex = v.childCount - 1
            for (i in 0 .. maxIndex) {
                //对子View执行applyRecursively方法
                v.getChildAt(i)?.let { applyRecursively(it, style) }
            }
        }
    }

将会调用ViewManager的ankoView方法。

anko默认支持的工厂

我们上面的用的构造工厂是sdk15提供的工厂,我们看看工厂里面的实现:

internal object `$$Anko$Factories$Sdk15ViewGroup` {
    val APP_WIDGET_HOST_VIEW = { ctx: Context -> _AppWidgetHostView(ctx) }
    val ABSOLUTE_LAYOUT = { ctx: Context -> _AbsoluteLayout(ctx) }
    val FRAME_LAYOUT = { ctx: Context -> _FrameLayout(ctx) }
    val GALLERY = { ctx: Context -> _Gallery(ctx) }
    val GRID_LAYOUT = { ctx: Context -> _GridLayout(ctx) }
    val GRID_VIEW = { ctx: Context -> _GridView(ctx) }
    val HORIZONTAL_SCROLL_VIEW = { ctx: Context -> _HorizontalScrollView(ctx) }
    val IMAGE_SWITCHER = { ctx: Context -> _ImageSwitcher(ctx) }
    val LINEAR_LAYOUT = { ctx: Context -> _LinearLayout(ctx) }
    val RADIO_GROUP = { ctx: Context -> _RadioGroup(ctx) }
    val RELATIVE_LAYOUT = { ctx: Context -> _RelativeLayout(ctx) }
    val SCROLL_VIEW = { ctx: Context -> _ScrollView(ctx) }
    val TABLE_LAYOUT = { ctx: Context -> _TableLayout(ctx) }
    val TABLE_ROW = { ctx: Context -> _TableRow(ctx) }
    val TEXT_SWITCHER = { ctx: Context -> _TextSwitcher(ctx) }
    val VIEW_ANIMATOR = { ctx: Context -> _ViewAnimator(ctx) }
    val VIEW_SWITCHER = { ctx: Context -> _ViewSwitcher(ctx) }
}

对于每一个View,内部都有一个相对应的构建方法。

open class _RelativeLayout(ctx: Context): RelativeLayout(ctx) {
     ...
     inline 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
    }
    ...
}

这个_RelativeLayout内部都是重载的lparams,也就是通过这个方法来创建布局的param属性。

ankoView的实现

inline fun <T : View> ViewManager.ankoView(factory: (ctx: Context) -> T, theme: Int, init: T.() -> Unit): T {
    //获取需要依附的context对象
    val ctx = AnkoInternals.wrapContextIfNeeded(AnkoInternals.getContext(this), theme)
    //通过工厂模式返回View
    val view = factory(ctx)
    view.init()
    //添加View,并返回
    AnkoInternals.addView(this, view)
    return view
}

这个ankoView将会接收工厂方法,返回View,然后执行init方法。这个init方法就行上面的工厂对象的构造函数。下面看看View添加

  fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
        //针对于根节点下的View
        is ViewGroup -> manager.addView(view)
        //针对于根节点
        is AnkoContext<*> -> manager.addView(view, null)
        else -> throw AnkoException("$manager is the wrong parent")
    }

将会直接添加到parent节点或者context下。

下面是一个自己理解的ankoView的绘制步骤:
[图片上传失败...(image-60d706-1542512107977)]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容