實戰Kotlin@Andorid(二): 界面構建與擴展方法
版權聲明
原作者:Doug Stevenson譯者:程大治本文由原作者授權翻譯並發布,首發於移動開發前線公眾號,未經允許禁止轉載。
在前面的文章中我們使用Kotlin中type-safe builder模式寫了一個還算有用的v方法,它可以構建任意Android View實例。
我們可以在其他Kotlin代碼中調用這個方法來創建並初始化任何類型的View:這真的實用嗎?
現在我們要創建一個很簡單的layout,它包含兩個TextView。在XML可以這樣表示:
我們可憐的v方法不能一下子創建這麼多,不過只需藉助一點幫助。我們需要再寫一個能夠將View添加至父View(LinearLayout, RelativeLayout)的方法。我們現在寫一個新的v方法。這個方法與原本的v方法幾乎一摸一樣,區別只在第一個參數上:不再是Context, 變成了ViewGroup類型。這個新的v方法需要持有父ViewGroup,以便將新創建的View對象在初始化並返回之前添加進其中。而新的v方法又能通過ViewGroup來獲取Context以初始化View,這樣就不用再傳入Context對象了。
現在我們看一下新的v方法如何與舊的協作來構建上述View層級。
這裡Kotlin代碼會像XML一樣嵌套,非常好看。
在上一部分中說過,Kotlin lambda with receiver可以在lambda內部以this關鍵字引用receiver對象。在上面的例子中,在外部v的lambda中receiver是LinearLayout,它作為第一個參數被傳入了兩個內部v方法(剛寫的v方法)。因為LinearLayout是ViewGroup的子類,Kotlin知道我們在調用新寫的v方法,因為舊的需要傳入Context。
通過這兩個兄弟v方法我們可以動態地、精確地創建嵌套View,其中的ViewGroup和View的具體類型均無限制。現在我們已經可以發現這種表述性的創建方式與XML有些相似,而在後續的文章中,我們也將發現Kotlin的速度要快一些。
提升空間
Kotlin的type-safe builder模式起了很大的作用,但是在很多時候,Kotlin還是比XML複雜不少。比如在Kotlin中當我們想設置一個TextView的maxWidth屬性為120dp時:
而在XML中,只需要:
<TextView android:maxWidith="120dp" />本來是為了簡化工作寫的v方法一下變麻煩了。這裡需要將dp轉化為px的簡便方法我在這裡想要一個方法可以將dp轉化為像素,然後上面的代碼最好能長這樣:這裡的方法可以接收一個以dp為單位的值,然後返回當前設備下轉化成像素的值。不過為什麼要叫這個方法dp_i而不是dp呢?在Android中有時會返回float而有時會返回int,我也不想再自己進行轉換,所以就給兩種返回類型都寫一個方法:"dp_i"和「dp_f」。
但在這裡仍有問題。如果你看一下剛才很醜的那段代碼,會發現計算像素值時需要Context對象。我可不想每次調用dp_i方法都傳入Context作為參數,所以在這裡要用到Kotlin的另一個技能:extension functions擴展方法。讓我們直接看一下擴展方法長什麼樣:
擴展方法如何工作?
你可能注意到的第一個點就是方法的前綴。你可能本以為第一個方法應該是dp_f,結果是View.dp_f。這是Kotlin中針對擴展方法的一個特殊語法。這裡將一個類名和一個方法名以點連接,而意思就是告訴Kotlin我們要給View類添加兩個新方法"dp_i","dp_f"。這樣使用擴展方法有幾點好處:
第一,在擴展方法內作為類的成員可以訪問其成員變數和方法(只有public和internal)。也就是說dp_f可以通過View內部的context屬性來訪問其Context引用。現在我們不需要將Context作為參數傳入了,因為它隱含在View中。
第二,在導入了(import)這些擴展方法的代碼段中可以像調用一個對象的普通方法一樣調用其擴展方法。在這裡,在v方法的lambda with receiver代碼塊中可以通過receiver View對象直接調用這些方法,像這樣:maxWidth = dp_i(120),Kotlin會識別出需要調用View類型的receiver對象的dp_i方法。
值得注意的一點是,Kotlin在聲明擴展方法時,不會修改其class。所以在這裡,View類的其他方法不能訪問擴展方法,因為擴展方法不是真正意義上的成員。
現在我們就有將dp轉成px的簡便方法了。
擴展方法還有其他的用處。現在我們已經看到通過擴展方法可以簡化一些棘手的代碼,我們利用這一點繼續簡化v方法。
現在我們有兩個v方法,第一個用於構建根元素,接收Context,第二個用於創建嵌套於父View中的子View。
inline fun <reified TV : View> v(context: Context, init: TV.() -> Unit) : TV
inline fun <reified TV : View> v(parent: ViewGroup, init: TV.() -> Unit) : TV如果我們不需要傳入Context或是ViewGroup作為參數,豈不是很好?通過擴展方法,我們就像剛才避免將Context傳入dp_f一樣重構這段代碼。下面使用擴展方法重新實現兩個v方法,注釋是兩個方法原本的聲明。
你可以看到我們去掉了兩個方法的第一個參數(Context和ViewGroup),並通過所繼承的類來獲取所需實例的引用。現在這兩個方法都只有一個參數:用於修改View的lambda with recceiver。
修改了方法後,如果我們在Activity(Context子類)中寫代碼,那就可以將v添加做Activity對象的成員。這樣我們就可以以這樣更簡單的方式構建嵌套View。
這裡調用v方法根本不像是在調用方法,因為我們不需要圓括弧。在第一部分中我說過,如果方法的最後一個參數是lambda,那就可以放在圓括弧後,而在這裡,只有一個參數,根本就不用寫圓括弧。
Kotlin中的擴展方法幫我們在代碼中很簡明易懂地創建構建View層級。不過還是有其他問題需要注意。比如我們想設置TextView的左內邊距為16dp。
在這裡調用setPadding()方法與直接修改屬性放在一起真是挺丑的,之所以有這樣的情況發生,是因為setPadding()方法有多個參數,並不是一個JavaBean風格的Setter方法。所以,Kotlin不能為其制定一個虛擬屬性。不用怕,我會在後續文章中通過Kotlin的另外一個功能來彌補這個問題。
原文鏈接:實戰Kotlin@Andorid(二):界面構建與擴展方法
推薦閱讀:
※如何評價開中文C# issue的人到kotliner.cn論壇提中文版Kotlin?
※哪一些大公司在使用 kotlin 開發應用?
※Kotlin傳教文 上
※Kotlin 語言培訓市場規模有多大?