Anko原理

ViewManager

在了解anko之前届榄,我們必須要先了解一下ViewManager這個(gè)類抡砂,這個(gè)是一個(gè)接口喉前,通過這個(gè)接口,我們可以在Activity中添加轻专、移除和更新View忆矛,我們可以通過 Context.getSystemService()來或者這個(gè)類。

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

也就是說请垛,只有實(shí)現(xiàn)了這個(gè)接口的類才能夠在activity中對(duì)view進(jìn)行操作催训。系統(tǒng)的ViewGroup就實(shí)現(xiàn)了這個(gè)接口。

AnkoContext

在anko中宗收,所有的View的操作是通過AnkoContext這個(gè)類來實(shí)現(xiàn) 的漫拭,所以,AnkoContext實(shí)現(xiàn)了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()
    }

一旦我們調(diào)用了updateViewLayout或者removeView方法,那么將會(huì)拋異常礼旅,UnsupportedOperationException膳叨,不支持這種操作。
這個(gè)類中包含了下面的三個(gè)參數(shù)

  • ctx: Context-- 上下文信息
  • owner: T-- 這個(gè)owner是這個(gè)UI的依附者痘系,可能是Activity菲嘴、fragment、viewHolder
  • view: View--AnkoComponent生成并返回的View
    下面來看看ankoContext提供的幾個(gè)靜態(tài)方法:
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個(gè)方法返回的都是AnkoContext實(shí)體汰翠,那么區(qū)別是什么龄坪?

  • create表示直接創(chuàng)建出AnkoContext,并且不能重用复唤,一旦已經(jīng)綁定了View健田,那么將拋出異常。
  • createReusable表示創(chuàng)建出可以復(fù)用的AnkoContext,如果一個(gè)AnkoContext已經(jīng)添加了子View佛纫,那么它會(huì)重新add View
  • createDelegate妓局;表示將view添加到相應(yīng)的委托對(duì)象中,用來在自定義View中代替inflate方法雳旅。
    我們來看看這幾個(gè)方法的實(shí)現(xiàn):

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,表示已經(jīng)添加了View了攒盈,如果是create方法調(diào)用的抵拘,那么將會(huì)拋出異常
            alreadyHasView()
        }

        this.myView = view

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

    private fun doAddView(context: Context, view: View) {
        when (context) {
            //找到activity型豁,然后執(zhí)行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實(shí)例

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

因?yàn)閏reateReusable表示創(chuàng)建的是可重用布局僵蛛,而AnkoContextImpl在已經(jīng)綁定了View的時(shí)候,將會(huì)通過alreadyHasView拋出異常迎变。所以ReusableAnkoContext通過復(fù)寫alreadyHasView充尉,并且來讓布局可重用。

DelegatingAnkoContext

createDelegate(owner: T)返回的是DelegatingAnkoContext實(shí)例

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會(huì)將view添加到owner上衣形,而不是Activity驼侠。

AnkoComponen

在Anko中,如果我們想要它的預(yù)覽特性谆吴,那么我們就需要用到AnkoComponen倒源,只有繼承了AnkoComponen,并且結(jié)合anko support句狼,就能?夠預(yù)覽了笋熬,我們需要在實(shí)現(xiàn)createView方法

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

我們通過createView方法繪制我們想要的View并返回。AnkoComponent有一個(gè)擴(kuò)展方法setContentView,是用來給Activity設(shè)置ContentView的腻菇。

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

擴(kuò)展

AnkoContext內(nèi)部添加了幾個(gè)擴(kuò)展函數(shù)

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的方式調(diào)用昔馋。

 UI { 
    .....
    }

然后內(nèi)部會(huì)調(diào)用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
    }

內(nèi)部還是調(diào)用AnkoContextImpl糖耸,并且調(diào)用內(nèi)部還是調(diào)用AnkoContextImpl的init方法秘遏,初始化UI,并添加ctx中.

AnkoUI布局的動(dòng)態(tài)創(chuàng)建

在了解AnkoUI的布局的創(chuàng)建之前蔬捷,我們需要先了解一下anko支持的dsl.

DSL(Domain-Specific-Language)

dsl指的是特定領(lǐng)域的語言垄提,kotlin的DSL特性支持就是擴(kuò)展榔袋,anko通過dsl周拐,才有了anko layout庫。

帶接收者的函數(shù)字面值

在kotlin中凰兑,支持給函數(shù)指定接收者對(duì)象妥粟,而無需額外的限定符,有點(diǎn)類似于擴(kuò)展函數(shù)吏够。

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

相當(dāng)于() -> Unit指定的接收者對(duì)象為_RelativeLayout
如果在函數(shù)體內(nèi)部可以調(diào)用接收者對(duì)象的方法勾给,那么假若這個(gè)方法又是帶接收者類型的方法,那么就可以不斷的往下調(diào)用了锅知。

anko布局

下面是一個(gè)簡(jiǎn)單的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添加了大部分組件的擴(kuò)展函數(shù),這個(gè)根節(jié)點(diǎn)relativeLayout將會(huì)調(diào)用到擴(kuò)展函數(shù)中播急,

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() }
}

在上面這個(gè)relativeLayout方法中,接收一個(gè)() -> Unit的lambada表達(dá)式售睹,這個(gè)表達(dá)式限定于relativeLayout桩警,所以這個(gè)參數(shù)就是relativelayout里面的元素,在上面的例子就是這個(gè)方法:

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

有一個(gè)注解AnkoViewDslMarker,這個(gè)參數(shù)會(huì)給對(duì)應(yīng)_RelativeLayout的View對(duì)象擴(kuò)展一個(gè)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擴(kuò)展T對(duì)象昌妹,并且接收一個(gè)f函數(shù)捶枢,在這里指的是View的init方法,會(huì)先執(zhí)行f方法飞崖,然后遍歷所有的子元素烂叔,并進(jìn)行創(chuàng)建。

 fun applyRecursively(v: View, style: (View) -> Unit) {
        //執(zhí)行init方法固歪,創(chuàng)建對(duì)象
        style(v)
        if (v is ViewGroup) {
        //如果是ViewGroup蒜鸡,那么可以添加子View,看看是否有子View
            val maxIndex = v.childCount - 1
            for (i in 0 .. maxIndex) {
                //對(duì)子View執(zhí)行applyRecursively方法
                v.getChildAt(i)?.let { applyRecursively(it, style) }
            }
        }
    }

將會(huì)調(diào)用ViewManager的ankoView方法牢裳。

anko默認(rèn)支持的工廠

我們上面的用的構(gòu)造工廠是sdk15提供的工廠逢防,我們看看工廠里面的實(shí)現(xiàn):

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

對(duì)于每一個(gè)View,內(nèi)部都有一個(gè)相對(duì)應(yīng)的構(gòu)建方法贰健。

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
    }
    ...
}

這個(gè)_RelativeLayout內(nèi)部都是重載的lparams胞四,也就是通過這個(gè)方法來創(chuàng)建布局的param屬性。

ankoView的實(shí)現(xiàn)

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

這個(gè)ankoView將會(huì)接收工廠方法辜伟,返回View氓侧,然后執(zhí)行init方法。這個(gè)init方法就行上面的工廠對(duì)象的構(gòu)造函數(shù)导狡。下面看看View添加

  fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
        //針對(duì)于根節(jié)點(diǎn)下的View
        is ViewGroup -> manager.addView(view)
        //針對(duì)于根節(jié)點(diǎn)
        is AnkoContext<*> -> manager.addView(view, null)
        else -> throw AnkoException("$manager is the wrong parent")
    }

將會(huì)直接添加到parent節(jié)點(diǎn)或者context下约巷。

下面是一個(gè)自己理解的ankoView的繪制步驟:
[圖片上傳失敗...(image-60d706-1542512107977)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市旱捧,隨后出現(xiàn)的幾起案子独郎,更是在濱河造成了極大的恐慌,老刑警劉巖枚赡,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氓癌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贫橙,警方通過查閱死者的電腦和手機(jī)贪婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卢肃,“玉大人疲迂,你說我怎么就攤上這事∧妫” “怎么了尤蒿?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幅垮。 經(jīng)常有香客問我腰池,道長,這世上最難降的妖魔是什么军洼? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任巩螃,我火速辦了婚禮,結(jié)果婚禮上匕争,老公的妹妹穿的比我還像新娘避乏。我一直安慰自己,他們只是感情好甘桑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布拍皮。 她就那樣靜靜地躺著,像睡著了一般跑杭。 火紅的嫁衣襯著肌膚如雪铆帽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天德谅,我揣著相機(jī)與錄音爹橱,去河邊找鬼。 笑死窄做,一個(gè)胖子當(dāng)著我的面吹牛愧驱,可吹牛的內(nèi)容都是我干的慰技。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼组砚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吻商!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起糟红,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤艾帐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盆偿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒爸,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年陈肛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揍鸟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兄裂。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡句旱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晰奖,到底是詐尸還是另有隱情谈撒,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布匾南,位于F島的核電站啃匿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛆楞。R本人自食惡果不足惜溯乒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豹爹。 院中可真熱鬧裆悄,春花似錦、人聲如沸臂聋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孩等。三九已至艾君,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肄方,已是汗流浹背冰垄。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留权她,地道東北人虹茶。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓冀瓦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親写烤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翼闽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容