1. 程式人生 > >Kotlin/DSL(Anko),原汁原味Kotlin開發Android---Activity Fragment與AnkoUI分離,強大的複用,更加便捷的開發

Kotlin/DSL(Anko),原汁原味Kotlin開發Android---Activity Fragment與AnkoUI分離,強大的複用,更加便捷的開發

/寫在前面

翻開自己的CSDN,已經很久很久沒有活動了,最近的關於PDF簽章的部落格還是16年寫的。將近年關,工作內容階段性告一段落,終於有時間寫一下自己的東西。

廢話少說,說說Kotlin。kotlin開發者給kotlin的定位---不是用來取代任何一種語言,而是 讓開發者有更多的選擇,更加便捷的開發自己的產品。kotlin不是學院派語言,而是工程性質的語言,kotlin並沒有帶給開發者更多的新奇的技術,而是把工程實踐中,開發者覺得很便捷的技術特性 整合到kotlin中。

最初沒有真正在專案中用到kotlin之前,我同大部分人的看法是一致的,以為kotlin無非是java的升級,語法糖而已。後來,抱著學習語言特性的想法,學習了kotlin,並用到自己專案中之後,才發現kotlin的真正牛掰之處。基於kotlin與java的無縫互操作,到目前為止,我已經很少寫java程式碼了。

下邊,開始kotlin + DSL(Anko),給大家開發時一個新思路

給大家介紹一下kotlin + anko的強大之處,文章比較長,希望對各位開發同學有所幫助

有所謂 仁者見仁智者見智,kotlin + anko還處於成長中,不一定適合所有開發環境。

我的建議是,可以在專案中 將xml和anko結合起來。 xml和anko各有優勢,並且anko可以用include包含xml,anko的優勢是 動態生成和複用。 二者結合,相信可以更加便捷的開發。

本文所用的原始碼demo,在碼雲上開源,地址如下

推薦幾個地址,本文不說kotlin語法基礎和如何構建專案,需要這方面的可以參考如下

開始

準備工作

開啟android studio,新建kotlin專案,在專案gradle中配置如下版本號
在app gradle中配置如下依賴, 協程 和 anko 可以依據自己需要的模組新增,具體可參考上述官方githubkotlin協程是非同步處理事件,比如網路請求等,是kotlin比java增加的新特性,可以像寫同步程式碼一樣寫非同步呼叫程式碼,熟悉C#和scala等語言的 對於協程應該都很熟悉了。對於本文的demo,沒有用到協程。後續如有機會,會單獨把協程寫一個部落格。接下來,就可以用kotlin+DSL進行開發了。

Anko開始

1、萬里之行,始於hello world

熟悉的步驟,新建MainActivity類,不需要建立 xml佈局檔案,也不需要setContentView接下來,直接在activity onCreate中寫hello world
OK了,就這麼簡單, 一個framelayout 佈局, 內嵌一個textView ,好了,如果現在執行到模擬器上,看到的樣子應該是

2、Anko佈局與Activity分離

anko基本實現方式

看了上邊的程式碼,是不是覺得有些疑惑,這是如何實現的呢? 讓我們在開始下一步介紹之前,先來看看framelayout和textView這兩個標籤的kotlin原始碼。其實,Kotlin官方幫我們構建了一個名為Anko的UI庫,基於Kotlin帶接收者的lambda表示式實現的。以framelayout為例,framelayout標籤 其實是anko定義的行內函數,熟悉kotlin的同學可以看出來,函式返回值就是android的framelayout。函式 入參init 是個帶接收者的lambda表示式,用來設定framelayout的屬性。看到這裡,大家應該明白了吧,說白了,其實就是新建framelayout類物件,再給framelayout物件設定屬性。基於kotlin的語法特性,看起來就像是 一個名叫framelayout的指令碼標籤。(其實gradle配置檔案也是如此,也是基於DSL(特定領域語言)實現的,只不過一般不去關注具體實現細節了)OK了,大家應該稍微明白寫了,為什麼 能再 activity onCreate中直接使用 framelayout,因為framelayout實際上是Activity的一個擴充套件函式,又framelayout函式的入參 只有一個 帶接收者的lambda表示式,根據kotlin的約定,函式呼叫時括號可以省略,最後一個lambda表示式可以寫在括號外邊,所以就變成了 framelayout{ } 這種形式。其實,anko不止為Activity定義了各個ui控制元件的擴充套件函式,也為其他例如 viewManager等類都定了擴充套件函式,基於此,我們可以將UI佈局 不寫到Activity裡,以免activity中UI程式碼過多,不便於管理。我們基於上邊說的anko封裝原理,可以將 UI程式碼寫到別的類中,然後在activity或fragment中呼叫。anko為了方便開發者寫出統一方便的程式碼,專門提供了一個封裝ui用的介面AnkoComponent,函式有個泛型T,用來指定是哪個activity/fragment的ui(當然也可以是BaseActivity或BaseFragment)。AnkoComponent有個抽象函式,createView,用來生成佈局。下邊我們將 剛開始寫的hello world修改一下,把framelayout和textview 從activity中分離出來。

使用AnkoComponent介面

新建類MainActivityUI 實現 AnkoComponent介面如圖,繼承後重寫createView函式,函式返回 View就是佈局的view, 入參ui 可以理解為對 MainActivity的封裝,裡邊有owner(MainActivity的例項),ctx(對應的context)等屬性。為了方便呼叫入參ui,我們將函式的實現寫法稍微修改一下,使用ui.apply{}表示式,如下這樣,我們就可以在apply表示式中 類似寫 xml佈局的形式,寫 anko佈局了。如下,但是,這裡使用的 framelayout函式,並不是 activity的擴充套件,而是viewManager的擴充套件,可以點開原始碼看到定義。不過,這對於使用者來說,並沒有什麼差異。接下來,就需要把UI 繫結到 對應的activity中,與 xml形式layout 類似,我們這樣在oncreate中寫在fragment中,類似的這樣OK,搞定, 執行到 模擬器中,同樣會看到剛開始的 hello world介面。順便說一下,如果你的android studio安裝的有anko support外掛的話,繼承自AnkoComponent的類,是可以像xml一樣預覽效果的(不過這個外掛目前還不是很強大,每次預覽,都需要build一下porject)

3、稍微進階,UI佈局中插入程式碼

各位看到這裡,估計心裡會有嘀咕,這不是和寫xml佈局檔案一樣麼,看不出來有什麼差別。甚至連預覽都那麼麻煩。OK,這只是表面現象,下邊,我們開始一步一步的揭示,anko的強大。

根據大家的經驗,xml檔案中只能寫靜態頁面,如果有動態需求,就需要用java類動態生成,且不說java類寫介面有多繁瑣,單是與xml的互動,就沒那麼容易。

但是,這些事情,對於kotlin/Anko來說,易如反掌,天生支援。

上文說過,雖然anko寫UI看起來類似xml中使用view標籤,但是anko中其實每個‘標籤’本質都是一個擴充套件函式,因此,在函式中寫程式碼,繫結事件,動態生成佈局,這是再正常不過的事情了,特別是anko為開發者封裝了很多方便的工具。

基於上述的hello world專案,我們先來個簡單的修改,給hello world繫結一個點選事件,土司一句話。如下截圖

是不是很簡單,這時,執行到模擬器上,textView就會有onclick事件了,彈出一個提示“我是hello world”。

同樣,可以在textView 之後 直接用常規的  .setOnClicklistioner呼叫,不過,那樣不麼是顯得囉嗦麼。

onClick函式和 toast函式是 anko封裝的擴充套件函式,就是為了減少開發時不必要的樣板程式碼。

有了這個操作, 對於只執行簡單操作的按鈕 等控制元件view,點選事件 就可以順手寫在ui裡。 比如只是為了跳轉頁面,直接在onclick中寫 startActivity就好了,省去了定義id findview等很多麻煩。

4、在activity中,使用view

看了上邊的介紹,大家一定有這樣的疑問,我們總不能把所有的程式碼都寫到 UI類中吧,那樣太傻了。

OK,下邊就看看我們怎樣在activity中操作view。

先在activity中定義view,然後在AnkoComponent中例項化view,最後,activity中就可以使用view了。

上文說過,AnkoComponent重寫的函式createView 的入參ui ,包含了繫結的activity/fragment的例項,基於此,我們可以如下實現

類似如下過程,

為了不進行多餘的 kotlin空指標處理,這裡把textView定義為懶初始化屬性。

然後在AnkoComponent中例項化,最後,就可以在 繫結ui之後 使用textView了。 

這樣,是不是省去了大量findView操作呢?  

當然,我們同樣可以 在UI中指定View的Id屬性,在activity中使用findView的方式來例項化。然而,我們多數時候不會這樣做,因為findViewbyId是比較消耗的操作,可以直接程式碼例項化的話,還是儘量不用findView。

配置View的id,不一定是為了findview才配置的。還有一個作用是在activity中區分不同的view,比如在activity中統一處理點選事件。

5、再次進階,使用自定義view

anko為我們封裝好了 android原生包括support庫design庫用到的所有控制元件,能夠完成我們大部分的工作。但是,很多時候我們需要自定義控制元件,那麼,我們怎麼在anko中使用自定義控制元件呢?

上文,有介紹framelayout原始碼的定義,其實,我們按照原始碼,就可以定義自己的anko控制元件了。

比如,我從網上找了一個 圓形imageview的自定義控制元件RoundImageView(java也好,kotlin也好,都不影響,因為kotlin與java無縫互操作),程式碼太長我就不貼了,可以到專案中檢視。

那麼,我需要怎樣配置,才能在anko中使用呢?

為了方便,我單獨為自定義view建一個配置用的kotlin file,叫ConfigMyView, 並仿照anko原始碼,定義RoundImageView的行內函數,如下截圖,由於我是在AnkoComponent中使用這個函式,所以我只聲明瞭ViewManager的擴充套件函式。

OK,有了這個定義後,我們就可以使用roundImageView標籤了。我們稍微修改一下hello world專案,把自定義的view加進入,把原來hello world刪除掉,把跟佈局改為relativeLayout。先從網上下載一張png圖片

然後,放到roundImageView裡,看看是什麼效果

好了,這樣,自定義view也可以用了。

6、重點來了,複用 複雜佈局,不僅僅是 複用佈局。

至此,我們基本可以正常使用akno進行開發了,但是,各位看官,是不是依舊沒有看出anko的優勢在哪裡?別急,如下,就是見證奇蹟的時刻。

根據經驗,很多時候,同一個專案中,同一個佈局樣式會在很多處都出現,為了使用方便,我們會單獨為這些佈局寫一個xml檔案,然後在不同的地方使用include 的方式引用。 anko同樣支援include操作引用xml檔案,來實現xml與anko的互動。

但是,anko能做的不僅僅是這樣。

我們來設定一個需求,做一個類似如下形式的輸入框。 輸入框背景可自定義,輸入框頭部圖示可自定義,輸入框提示文字可自定義,輸入框會判斷輸入內容正確性,正確顯示對號,錯誤顯示紅叉,輸入時 顯示灰色叉子,點選灰色叉子可以刪除輸入框內容。

然後,在專案中,這種型別的輸入框,有好多個,那麼怎麼辦?

我們想想這個要怎麼實現,如果使用傳統的xml方式, 我們可以單獨寫一個輸入框佈局,然後在每次include後,在activity中一個一個findview,然後設定對應控制元件的屬性,例如頭部圖示圖片,對號叉子圖片,輸入框提示內容,並且要指定輸入框各個事件,讓對號紅叉可以正常顯示 等等等。

上述過程,光想想都夠酸爽,如果 專案中多處用到,呵呵。。。

當然,如果您是大拿,說直接自定義view實現,那我拜服。

ok,來看看使用kotlin anko可以怎樣實現?

①準備資源
背景可以是圖片或者 drawable xml檔案,為了方便,我就不用圖片了,我寫一個drawable xml,如下準備輸入框頭部的圖示,我下載個手機的圖示,起名叫做phone.png

然後再下載3個圖示 對號 ,紅叉子,灰色叉子 作為 輸入框尾部的圖示

② anko書寫佈局,並繫結事件
準備好資源,我們下邊新建一個類,普通類MyInputEdit,成員變數如下,這個可以自己根據需要增加或減少,有幾個成員變數我們給了預設值,因為這幾個不需要經常變化。另外,為了後續方便 單獨設定某個控制元件的屬性,我們再增加幾個成員變數,總體如下

下邊,我們根據需求,分析對應的輸入框佈局。

最外層linearlayout水平佈局,頭部放一個imageview,中間放個edittext,尾部 framelayout 中放3個imageview,OK,其實佈局結構很簡單,我們在MyInputEdit中新增一個函式getInputEdit(),用來生成這樣的佈局,入參viewManager。

接下來,就是分析事件綁定了,我們簡單的實現一下。

1、edittext失去焦點時,判斷 輸入正確 錯誤,如果沒有輸入,則什麼都不顯示

2、在文字編輯時,顯示 灰色叉子,一鍵清空輸入內容

因此,edit需要繫結 onFocusChangeListener 和 textChangedListener 

尾部刪除imageView需要繫結點選事件

為了方便控制 尾部3個imageview的顯示,我們增加一個函式來控制showInputCheckIcon

ok,讓我們先來看 edit的onFocusChangeListener中的邏輯

再看看textChangeedListener中的邏輯

刪除按鈕的點選事件很簡單,點選後 清空 edittext就行了

ok,完整的 函式程式碼,可以到 專案中檢視,太長,就不貼了

除此之外,我們再增加一個獲取輸入內容的函式,方便後續使用

OK,至此,MyInputEdit類基本完成,整體如下

③AnkoComponent UI類中使用MyInputEdit類
在UI類中,使用如下截圖,很簡單紅框部分,怎麼樣,程式碼是不是很簡單, 幾句程式碼,就可以滿足我們的需求,不僅僅是佈局樣式,就連各個事件也OK了。

ok,我們再來一個 密碼輸入框,同樣的方式,但因為是密碼,我們需要多傳一個引數,輸入型別

執行後,如下效果

基於此,一般連同 註冊頁面, 登陸頁面, 忘記密碼頁面,修改密碼頁面,我們可能需要很多個類似的輸入框,使用kotlin anko,可以輕鬆的把實現 複雜的 輸入框需求,並且不需要自定義view。

看到這裡,您是不是開始有些明白anko的強大之處了呢?

7、繼續進階,使用函式來定義各種型別的佈局複用,不僅僅是佈局

我們來看看另外一種,實現佈局複用的方式-----直接定義佈局函式

第6節中,因為 子佈局有很多事件需要處理,所以,我們直接建一個類來方便事件繫結

然而,對於一般沒有很多事件處理的 複用佈局,我們可以直接定義函式來進行復用。

比如這樣的需求,我們專案中可能有 很多類似如下的 動作條 ,有時帶有頭部圖示,優勢帶有尾部圖示

並且有的還可以顯示使用者內容,使用者內容可能是圖片或者文字,文字有可能要經過特殊處理(如截圖中手機號不完全顯示),動作條本身又有點選事件。

我們思考一下,如果用xml layout檔案我們要怎麼實現?用java動態佈局要怎麼實現?是不是很酸爽?當然,如果同一頁有多個排列的這樣的佈局,我們可以用 adapter view展示,比如listview等。如果每一頁只需要一兩個這樣的佈局,但是很多頁都需要,那用adapterview就有點 高射炮打蒼蠅的感覺了。ok,我們看看使用 kotlin函式怎麼實現還是那樣,先分析需求1、總體linearlayout + 需要的背景2、頭部圖示 ,可以控制是否顯示3、頭部文字,可以修改4、使用者資訊部分,可以自定義是什麼樣的view ,可以是文字 或者圖片,可以控制是否顯示5、尾部的箭頭 可以自定義圖示 並控制是否顯示

基於此,我們定義一個類似如下的行內函數, 入參根據需求,暫時定如下這幾個

由於 顯示使用者資訊的部分,有可能是圖片或者 文字,或者其他資訊,所以我們給函式定義一個泛型T,View型別, 入參部分預留T的init初始化 lambda表示式。
函式的實現部分,就是 堆佈局了,如下OK,佈局函式定義完畢,那麼,我們在AnkoComponent中怎麼使用呢?很簡單,類似MyInputEdit類一樣,直接到 UI程式碼中寫就好了。在 AnkoComponent中 依次新增 需求截圖中的,使用者頭像顯示, 使用者手機號顯示,設定  3個動作條
如下執行之後的效果如下,還有點選事件

結束語

好了,看到這裡,大家估計也明白anko的優勢所在了,就是 佈局與程式碼的 巢狀,基於此,我們可以很方便的用簡單的方式 複用很多ui程式碼,動態生成ui等等。後續有時間的話,還會繼續介紹kotlin,強化自己的學習,順便分享所得。感謝!再次,android UI可以用xml與anko混合開發,各取所長,相信會帶給大家開發的便利。這一篇就簡單介紹到這裡,由於我水平有限,文中難免有錯誤和不足,歡迎指摘,感謝!-------------------------------------------------------------akai.liu -- 2018-02-08